diff --git a/.goreleaser.yaml b/.goreleaser.yaml index cf61b678..149a7495 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -15,7 +15,7 @@ builds: - id: build main: ./cmd/answer/. binary: answer - ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser + ldflags: -s -w -X github.com/answerdev/answer/cmd.Version={{.Version}} -X github.com/answerdev/answer/cmd.Revision={{.ShortCommit}} -X github.com/answerdev/answer/cmd.Time={{.Date}} -X main.BuildUser=goreleaser flags: -v goos: - linux @@ -26,7 +26,7 @@ builds: - id: build-windows main: ./cmd/answer/. binary: answer - ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser + ldflags: -s -w -X github.com/answerdev/answer/cmd.Version={{.Version}} -X github.com/answerdev/answer/cmd.Revision={{.ShortCommit}} -X github.com/answerdev/answer/cmd.Time={{.Date}} -X main.BuildUser=goreleaser flags: -v goos: - windows diff --git a/cmd/answer/wire_gen.go b/cmd/answer/wire_gen.go index aafd3dc4..bbb6e1ec 100644 --- a/cmd/answer/wire_gen.go +++ b/cmd/answer/wire_gen.go @@ -165,8 +165,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo) answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo) questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData) - questionController := controller.NewQuestionController(questionService, rankService) answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService) + questionController := controller.NewQuestionController(questionService, answerService, rankService) dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData) answerController := controller.NewAnswerController(answerService, rankService, dashboardService) searchParser := search_parser.NewSearchParser(tagCommonService, userCommon) diff --git a/docs/docs.go b/docs/docs.go index d1fa0a96..4622228b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -3063,6 +3063,45 @@ const docTemplate = `{ } } }, + "/answer/api/v1/question/answer": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question and answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "add question and answer", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionAddByAnswer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/api/v1/question/closemsglist": { "get": { "security": [ @@ -6751,6 +6790,41 @@ const docTemplate = `{ } } }, + "schema.QuestionAddByAnswer": { + "type": "object", + "required": [ + "answer_content", + "content", + "tags", + "title" + ], + "properties": { + "answer_content": { + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 150, + "minLength": 6 + } + } + }, "schema.QuestionPageReq": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 1ade7434..240e543f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3051,6 +3051,45 @@ } } }, + "/answer/api/v1/question/answer": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question and answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "add question and answer", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionAddByAnswer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/api/v1/question/closemsglist": { "get": { "security": [ @@ -6739,6 +6778,41 @@ } } }, + "schema.QuestionAddByAnswer": { + "type": "object", + "required": [ + "answer_content", + "content", + "tags", + "title" + ], + "properties": { + "answer_content": { + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 150, + "minLength": 6 + } + } + }, "schema.QuestionPageReq": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6c17b0c8..7aa79508 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1019,6 +1019,33 @@ definitions: - tags - title type: object + schema.QuestionAddByAnswer: + properties: + answer_content: + maxLength: 65535 + minLength: 6 + type: string + content: + description: content + maxLength: 65535 + minLength: 6 + type: string + tags: + description: tags + items: + $ref: '#/definitions/schema.TagItem' + type: array + title: + description: question title + maxLength: 150 + minLength: 6 + type: string + required: + - answer_content + - content + - tags + - title + type: object schema.QuestionPageReq: properties: orderCond: @@ -3796,6 +3823,30 @@ paths: summary: update question tags: - Question + /answer/api/v1/question/answer: + post: + consumes: + - application/json + description: add question and answer + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionAddByAnswer' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: add question and answer + tags: + - Question /answer/api/v1/question/closemsglist: get: consumes: diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go index 55b3895e..93d34f5b 100644 --- a/internal/controller/answer_controller.go +++ b/internal/controller/answer_controller.go @@ -138,7 +138,6 @@ func (ac *AnswerController) Add(ctx *gin.Context) { return } if !has { - // todo !has handler.HandleResponse(ctx, nil, nil) return } diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go index 1c36e974..cefa075a 100644 --- a/internal/controller/question_controller.go +++ b/internal/controller/question_controller.go @@ -14,18 +14,28 @@ import ( "github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/pkg/uid" "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" "github.com/segmentfault/pacman/errors" ) // QuestionController question controller type QuestionController struct { questionService *service.QuestionService + answerService *service.AnswerService rankService *rank.RankService } // NewQuestionController new controller -func NewQuestionController(questionService *service.QuestionService, rankService *rank.RankService) *QuestionController { - return &QuestionController{questionService: questionService, rankService: rankService} +func NewQuestionController( + questionService *service.QuestionService, + answerService *service.AnswerService, + rankService *rank.RankService, +) *QuestionController { + return &QuestionController{ + questionService: questionService, + answerService: answerService, + rankService: rankService, + } } // RemoveQuestion delete question @@ -281,6 +291,109 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } +// AddQuestionByAnswer add question +// @Summary add question and answer +// @Description add question and answer +// @Tags Question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.QuestionAddByAnswer true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question/answer [post] +func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) { + req := &schema.QuestionAddByAnswer{} + errFields := handler.BindAndCheckReturnErr(ctx, req) + if ctx.IsAborted() { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ + permission.QuestionAdd, + permission.QuestionEdit, + permission.QuestionDelete, + permission.QuestionClose, + permission.QuestionReopen, + permission.TagUseReservedTag, + }) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + req.CanAdd = canList[0] + req.CanEdit = canList[1] + req.CanDelete = canList[2] + req.CanClose = canList[3] + req.CanReopen = canList[4] + req.CanUseReservedTag = canList[5] + if !req.CanAdd { + handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil) + return + } + questionReq := new(schema.QuestionAdd) + err = copier.Copy(questionReq, req) + if err != nil { + handler.HandleResponse(ctx, errors.Forbidden(reason.RequestFormatError), nil) + return + } + errList, err := qc.questionService.CheckAddQuestion(ctx, questionReq) + if err != nil { + errlist, ok := errList.([]*validator.FormErrorField) + if ok { + errFields = append(errFields, errlist...) + } + } + + if len(errFields) > 0 { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields) + return + } + + resp, err := qc.questionService.AddQuestion(ctx, questionReq) + if err != nil { + errlist, ok := resp.([]*validator.FormErrorField) + if ok { + errFields = append(errFields, errlist...) + } + } + + if len(errFields) > 0 { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields) + return + } + //add the question id to the answer + questionInfo, ok := resp.(*schema.QuestionInfo) + if ok { + answerReq := &schema.AnswerAddReq{} + answerReq.QuestionID = uid.DeShortID(questionInfo.ID) + answerReq.UserID = middleware.GetLoginUserIDFromContext(ctx) + answerReq.Content = req.AnswerContent + answerReq.HTML = req.AnswerHTML + answerID, err := qc.answerService.Insert(ctx, answerReq) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + info, questionInfo, has, err := qc.answerService.Get(ctx, answerID, req.UserID) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !has { + handler.HandleResponse(ctx, nil, nil) + return + } + handler.HandleResponse(ctx, err, gin.H{ + "info": info, + "question": questionInfo, + }) + return + } + + handler.HandleResponse(ctx, err, resp) +} + // UpdateQuestion update question // @Summary update question // @Description update question diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index dc3c271d..3ff5f873 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -186,6 +186,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) { // question r.POST("/question", a.questionController.AddQuestion) + r.POST("/question/answer", a.questionController.AddQuestionByAnswer) r.PUT("/question", a.questionController.UpdateQuestion) r.DELETE("/question", a.questionController.RemoveQuestion) r.PUT("/question/status", a.questionController.CloseQuestion) diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index c743577b..19a2c3fd 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -63,6 +63,33 @@ func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err erro return nil, nil } +type QuestionAddByAnswer struct { + // question title + Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` + // content + Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` + // html + HTML string `json:"-"` + AnswerContent string `validate:"required,notblank,gte=6,lte=65535" json:"answer_content"` + AnswerHTML string `json:"-"` + // tags + Tags []*TagItem `validate:"required,dive" json:"tags"` + // user id + UserID string `json:"-"` + QuestionPermission +} + +func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, err error) { + req.HTML = converter.Markdown2HTML(req.Content) + req.AnswerHTML = converter.Markdown2HTML(req.AnswerContent) + for _, tag := range req.Tags { + if len(tag.OriginalText) > 0 { + tag.ParsedText = converter.Markdown2HTML(tag.OriginalText) + } + } + return nil, nil +} + type QuestionPermission struct { // whether user can add it CanAdd bool `json:"-"` diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go index 38595229..1b65c48d 100644 --- a/internal/service/comment/comment_service.go +++ b/internal/service/comment/comment_service.go @@ -20,7 +20,6 @@ import ( "github.com/answerdev/answer/pkg/encryption" "github.com/answerdev/answer/pkg/htmltext" "github.com/answerdev/answer/pkg/uid" - "github.com/davecgh/go-spew/spew" "github.com/jinzhu/copier" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" @@ -448,7 +447,6 @@ func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *s if err != nil { log.Error(err) } else { - spew.Dump("==", objInfo) commentResp.ObjectType = objInfo.ObjectType commentResp.Title = objInfo.Title commentResp.UrlTitle = htmltext.UrlTitle(objInfo.Title) diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index f8c40cf5..a0d039fc 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -335,12 +335,15 @@ func (qs *QuestionCommon) FormatQuestionsPage( } else { item.Tags = make([]*schema.TagResp, 0) } - userInfo := userInfoMap[item.Operator.ID] - if userInfo != nil { - item.Operator.DisplayName = userInfo.DisplayName - item.Operator.Username = userInfo.Username - item.Operator.Rank = userInfo.Rank + userInfo, ok := userInfoMap[item.Operator.ID] + if ok { + if userInfo != nil { + item.Operator.DisplayName = userInfo.DisplayName + item.Operator.Username = userInfo.Username + item.Operator.Rank = userInfo.Rank + } } + } return formattedQuestions, nil } diff --git a/pkg/htmltext/htmltext_test.go b/pkg/htmltext/htmltext_test.go index f67a20db..ae52cc1a 100644 --- a/pkg/htmltext/htmltext_test.go +++ b/pkg/htmltext/htmltext_test.go @@ -64,6 +64,5 @@ func TestUrlTitle(t *testing.T) { for _, title := range list { formatTitle := UrlTitle(title) spew.Dump(formatTitle) - } }