From b0d2cdc4a04a8a192fd150216e33b3b53b9544b3 Mon Sep 17 00:00:00 2001 From: aichy126 <16996097+aichy126@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:17:17 +0800 Subject: [PATCH] add question operation --- docs/docs.go | 54 ++++++++++++++++++ docs/swagger.json | 54 ++++++++++++++++++ docs/swagger.yaml | 34 +++++++++++ internal/controller/question_controller.go | 39 +++++++++++++ internal/migrations/migrations.go | 1 + internal/migrations/v8.go | 56 +++++++++++++++++++ internal/router/answer_api_router.go | 1 + internal/schema/question_schema.go | 18 +++++- .../service/permission/permission_name.go | 2 + 9 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 internal/migrations/v8.go diff --git a/docs/docs.go b/docs/docs.go index 4622228b..73ff9ecd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -3168,6 +3168,45 @@ const docTemplate = `{ } } }, + "/answer/api/v1/question/operation": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Operation question \\n operation [pin unpin hide show]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "Operation question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OperationQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/api/v1/question/page": { "get": { "description": "get questions by page", @@ -6739,6 +6778,21 @@ const docTemplate = `{ } } }, + "schema.OperationQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "operation": { + "description": "operation [pin unpin hide show]", + "type": "string" + } + } + }, "schema.PermissionMemberAction": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 240e543f..75078211 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3156,6 +3156,45 @@ } } }, + "/answer/api/v1/question/operation": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Operation question \\n operation [pin unpin hide show]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "Operation question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OperationQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/api/v1/question/page": { "get": { "description": "get questions by page", @@ -6727,6 +6766,21 @@ } } }, + "schema.OperationQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "operation": { + "description": "operation [pin unpin hide show]", + "type": "string" + } + } + }, "schema.PermissionMemberAction": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7aa79508..200abf16 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -983,6 +983,16 @@ definitions: description: inbox achievement type: string type: object + schema.OperationQuestionReq: + properties: + id: + type: string + operation: + description: operation [pin unpin hide show] + type: string + required: + - id + type: object schema.PermissionMemberAction: properties: action: @@ -3888,6 +3898,30 @@ paths: summary: get question details tags: - Question + /answer/api/v1/question/operation: + put: + consumes: + - application/json + description: Operation question \n operation [pin unpin hide show] + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.OperationQuestionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Operation question + tags: + - Question /answer/api/v1/question/page: get: consumes: diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go index cefa075a..5ab5f613 100644 --- a/internal/controller/question_controller.go +++ b/internal/controller/question_controller.go @@ -70,6 +70,45 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) { handler.HandleResponse(ctx, err, nil) } +// OperationQuestion Operation question +// @Summary Operation question +// @Description Operation question \n operation [pin unpin hide show] +// @Tags Question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.OperationQuestionReq true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question/operation [put] +func (qc *QuestionController) OperationQuestion(ctx *gin.Context) { + req := &schema.OperationQuestionReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.ID = uid.DeShortID(req.ID) + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ + permission.QuestionPin, + permission.QuestionHide, + }) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + req.CanPin = canList[0] + req.CanList = canList[1] + if (req.Operation == schema.QuestionOperationPin || req.Operation == schema.QuestionOperationUnPin) && !req.CanPin { + handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil) + return + } + if (req.Operation == schema.QuestionOperationHide || req.Operation == schema.QuestionOperationShow) && !req.CanList { + handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil) + return + } + + handler.HandleResponse(ctx, nil, nil) +} + // CloseQuestion Close question // @Summary Close question // @Description Close question diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index 7b53293e..00ae9bc0 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -56,6 +56,7 @@ var migrations = []Migration{ NewMigration("add user role", addRoleFeatures, false), NewMigration("add theme and private mode", addThemeAndPrivateMode, true), NewMigration("add new answer notification", addNewAnswerNotification, true), + NewMigration("add user pin hide features", addRolePinAndHideFeatures, true), } // GetCurrentDBVersion returns the current db version diff --git a/internal/migrations/v8.go b/internal/migrations/v8.go new file mode 100644 index 00000000..325738d0 --- /dev/null +++ b/internal/migrations/v8.go @@ -0,0 +1,56 @@ +package migrations + +import ( + "github.com/answerdev/answer/internal/entity" + "github.com/answerdev/answer/internal/service/permission" + "xorm.io/xorm" +) + +func addRolePinAndHideFeatures(x *xorm.Engine) error { + + powers := []*entity.Power{ + {ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "Top or untop the question"}, + {ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide or show the question"}, + } + // insert default powers + for _, power := range powers { + exist, err := x.Get(&entity.Power{ID: power.ID}) + if err != nil { + return err + } + if exist { + _, err = x.ID(power.ID).Update(power) + } else { + _, err = x.Insert(power) + } + if err != nil { + return err + } + } + + rolePowerRels := []*entity.RolePowerRel{ + + {RoleID: 2, PowerType: permission.QuestionPin}, + {RoleID: 2, PowerType: permission.QuestionHide}, + + {RoleID: 3, PowerType: permission.QuestionPin}, + {RoleID: 3, PowerType: permission.QuestionHide}, + } + + // insert default powers + for _, rel := range rolePowerRels { + exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType}) + if err != nil { + return err + } + if exist { + continue + } + _, err = x.Insert(rel) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 3ff5f873..3f79d2f2 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -190,6 +190,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) { r.PUT("/question", a.questionController.UpdateQuestion) r.DELETE("/question", a.questionController.RemoveQuestion) r.PUT("/question/status", a.questionController.CloseQuestion) + r.PUT("/question/operation", a.questionController.OperationQuestion) r.PUT("/question/reopen", a.questionController.ReopenQuestion) r.GET("/question/similar", a.questionController.SearchByTitleLike) diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index 19a2c3fd..d9bbf76f 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -8,9 +8,13 @@ import ( ) const ( - SitemapMaxSize = 50000 - SitemapCachekey = "answer@sitemap" - SitemapPageCachekey = "answer@sitemap@page%d" + SitemapMaxSize = 50000 + SitemapCachekey = "answer@sitemap" + SitemapPageCachekey = "answer@sitemap@page%d" + QuestionOperationPin = "pin" + QuestionOperationUnPin = "unpin" + QuestionOperationHide = "hide" + QuestionOperationShow = "show" ) // RemoveQuestionReq delete question request @@ -28,6 +32,14 @@ type CloseQuestionReq struct { UserID string `json:"-"` // user_id } +type OperationQuestionReq struct { + ID string `validate:"required" json:"id"` + Operation string `json:"operation"` // operation [pin unpin hide show] + UserID string `json:"-"` // user_id + CanPin bool `json:"-"` + CanList bool `json:"-"` +} + type CloseQuestionMeta struct { CloseType int `json:"close_type"` CloseMsg string `json:"close_msg"` diff --git a/internal/service/permission/permission_name.go b/internal/service/permission/permission_name.go index 4a62ec86..29509f84 100644 --- a/internal/service/permission/permission_name.go +++ b/internal/service/permission/permission_name.go @@ -10,6 +10,8 @@ const ( QuestionReopen = "question.reopen" QuestionVoteUp = "question.vote_up" QuestionVoteDown = "question.vote_down" + QuestionPin = "question.pin" //Top or untop the question + QuestionHide = "question.hide" //hide or show the question AnswerAdd = "answer.add" AnswerEdit = "answer.edit" AnswerEditWithoutReview = "answer.edit_without_review"