From 3428b5fbc3b62c00747627d4f9320f0e21787279 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Thu, 25 May 2023 15:56:58 +0800 Subject: [PATCH 1/4] feat(permission): add user permission API --- cmd/wire_gen.go | 3 +- docs/docs.go | 96 ++++++++++++++++++++ docs/swagger.json | 96 ++++++++++++++++++++ docs/swagger.yaml | 72 +++++++++++++++ internal/controller/controller.go | 1 + internal/controller/cron_controller.go | 1 - internal/controller/permission_controller.go | 47 ++++++++++ internal/router/answer_api_router.go | 6 ++ internal/schema/permission.go | 20 +++- 9 files changed, 338 insertions(+), 4 deletions(-) delete mode 100644 internal/controller/cron_controller.go create mode 100644 internal/controller/permission_controller.go diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index f50f34a2..89f943bf 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -211,7 +211,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData) pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configRepo) pluginController := controller_admin.NewPluginController(pluginCommonService) - answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController) + permissionController := controller.NewPermissionController(rankService) + answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController) swaggerRouter := router.NewSwaggerRouter(swaggerConf) uiRouter := router.NewUIRouter(siteinfoController, siteInfoCommonService) authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService) diff --git a/docs/docs.go b/docs/docs.go index 2c3d72ea..5601f442 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -3009,6 +3009,102 @@ const docTemplate = `{ } } }, + "/answer/api/v1/permission": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "check user permission", + "produces": [ + "application/json" + ], + "tags": [ + "Permission" + ], + "summary": "check user permission", + "parameters": [ + { + "type": "string", + "description": "access-token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "enum": [ + "question.add", + "question.edit", + "question.edit_without_review", + "question.delete", + "question.close", + "question.reopen", + "question.vote_up", + "question.vote_down", + "question.pin", + "question.unpin", + "question.hide", + "question.show", + "answer.add", + "answer.edit", + "answer.edit_without_review", + "answer.delete", + "answer.accept", + "answer.vote_up", + "answer.vote_down", + "answer.invite_someone_to_answer", + "comment.add", + "comment.edit", + "comment.delete", + "comment.vote_up", + "comment.vote_down", + "report.add", + "tag.add", + "tag.edit", + "tag.edit_slug_name", + "tag.edit_without_review", + "tag.delete", + "tag.synonym", + "link.url_limit", + "vote.detail", + "answer.audit", + "question.audit", + "tag.audit", + "tag.use_reserved_tag" + ], + "type": "string", + "description": "permission key", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/personal/answer/page": { "get": { "security": [ diff --git a/docs/swagger.json b/docs/swagger.json index a9cb60df..5b1f3686 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2997,6 +2997,102 @@ } } }, + "/answer/api/v1/permission": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "check user permission", + "produces": [ + "application/json" + ], + "tags": [ + "Permission" + ], + "summary": "check user permission", + "parameters": [ + { + "type": "string", + "description": "access-token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "enum": [ + "question.add", + "question.edit", + "question.edit_without_review", + "question.delete", + "question.close", + "question.reopen", + "question.vote_up", + "question.vote_down", + "question.pin", + "question.unpin", + "question.hide", + "question.show", + "answer.add", + "answer.edit", + "answer.edit_without_review", + "answer.delete", + "answer.accept", + "answer.vote_up", + "answer.vote_down", + "answer.invite_someone_to_answer", + "comment.add", + "comment.edit", + "comment.delete", + "comment.vote_up", + "comment.vote_down", + "report.add", + "tag.add", + "tag.edit", + "tag.edit_slug_name", + "tag.edit_without_review", + "tag.delete", + "tag.synonym", + "link.url_limit", + "vote.detail", + "answer.audit", + "question.audit", + "tag.audit", + "tag.use_reserved_tag" + ], + "type": "string", + "description": "permission key", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/personal/answer/page": { "get": { "security": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7216dfd2..13d4db4c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -4086,6 +4086,78 @@ paths: summary: DelRedDot tags: - Notification + /answer/api/v1/permission: + get: + description: check user permission + parameters: + - description: access-token + in: header + name: Authorization + required: true + type: string + - description: permission key + enum: + - question.add + - question.edit + - question.edit_without_review + - question.delete + - question.close + - question.reopen + - question.vote_up + - question.vote_down + - question.pin + - question.unpin + - question.hide + - question.show + - answer.add + - answer.edit + - answer.edit_without_review + - answer.delete + - answer.accept + - answer.vote_up + - answer.vote_down + - answer.invite_someone_to_answer + - comment.add + - comment.edit + - comment.delete + - comment.vote_up + - comment.vote_down + - report.add + - tag.add + - tag.edit + - tag.edit_slug_name + - tag.edit_without_review + - tag.delete + - tag.synonym + - link.url_limit + - vote.detail + - answer.audit + - question.audit + - tag.audit + - tag.use_reserved_tag + in: query + name: action + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + additionalProperties: + type: boolean + type: object + type: object + security: + - ApiKeyAuth: [] + summary: check user permission + tags: + - Permission /answer/api/v1/personal/answer/page: get: consumes: diff --git a/internal/controller/controller.go b/internal/controller/controller.go index a5daabba..6b1c2fad 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -26,4 +26,5 @@ var ProviderSetController = wire.NewSet( NewTemplateController, NewConnectorController, NewUserCenterController, + NewPermissionController, ) diff --git a/internal/controller/cron_controller.go b/internal/controller/cron_controller.go deleted file mode 100644 index b0b429f8..00000000 --- a/internal/controller/cron_controller.go +++ /dev/null @@ -1 +0,0 @@ -package controller diff --git a/internal/controller/permission_controller.go b/internal/controller/permission_controller.go new file mode 100644 index 00000000..5dc6a46b --- /dev/null +++ b/internal/controller/permission_controller.go @@ -0,0 +1,47 @@ +package controller + +import ( + "github.com/answerdev/answer/internal/base/handler" + "github.com/answerdev/answer/internal/base/middleware" + "github.com/answerdev/answer/internal/schema" + "github.com/answerdev/answer/internal/service/rank" + "github.com/gin-gonic/gin" +) + +type PermissionController struct { + rankService *rank.RankService +} + +// NewPermissionController new language controller. +func NewPermissionController(rankService *rank.RankService) *PermissionController { + return &PermissionController{rankService: rankService} +} + +// GetPermission check user permission +// @Summary check user permission +// @Description check user permission +// @Tags Permission +// @Security ApiKeyAuth +// @Param Authorization header string true "access-token" +// @Produce json +// @Param action query string true "permission key" Enums(question.add, question.edit, question.edit_without_review, question.delete, question.close, question.reopen, question.vote_up, question.vote_down, question.pin, question.unpin, question.hide, question.show, answer.add, answer.edit, answer.edit_without_review, answer.delete, answer.accept, answer.vote_up, answer.vote_down, answer.invite_someone_to_answer, comment.add, comment.edit, comment.delete, comment.vote_up, comment.vote_down, report.add, tag.add, tag.edit, tag.edit_slug_name, tag.edit_without_review, tag.delete, tag.synonym, link.url_limit, vote.detail, answer.audit, question.audit, tag.audit, tag.use_reserved_tag) +// @Success 200 {object} handler.RespBody{data=map[string]bool} +// @Router /answer/api/v1/permission [get] +func (u *PermissionController) GetPermission(ctx *gin.Context) { + req := &schema.GetPermissionReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + userID := middleware.GetLoginUserIDFromContext(ctx) + resp, err := u.rankService.CheckOperationPermissions(ctx, userID, req.Actions) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + mapping := make(map[string]bool, len(resp)) + for i, action := range req.Actions { + mapping[action] = resp[i] + } + handler.HandleResponse(ctx, err, mapping) +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 065373ac..025ee594 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -33,6 +33,7 @@ type AnswerAPIRouter struct { activityController *controller.ActivityController roleController *controller_admin.RoleController pluginController *controller_admin.PluginController + permissionController *controller.PermissionController } func NewAnswerAPIRouter( @@ -61,6 +62,7 @@ func NewAnswerAPIRouter( activityController *controller.ActivityController, roleController *controller_admin.RoleController, pluginController *controller_admin.PluginController, + permissionController *controller.PermissionController, ) *AnswerAPIRouter { return &AnswerAPIRouter{ langController: langController, @@ -88,6 +90,7 @@ func NewAnswerAPIRouter( activityController: activityController, roleController: roleController, pluginController: pluginController, + permissionController: permissionController, } } @@ -220,6 +223,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) { // reason r.GET("/reasons", a.reasonController.Reasons) + // permission + r.GET("/permission", a.permissionController.GetPermission) + // notification r.GET("/notification/status", a.notificationController.GetRedDot) r.PUT("/notification/status", a.notificationController.ClearRedDot) diff --git a/internal/schema/permission.go b/internal/schema/permission.go index 2652acfa..91f4a0f9 100644 --- a/internal/schema/permission.go +++ b/internal/schema/permission.go @@ -1,7 +1,10 @@ package schema -const PermissionMemberActionTypeEdit = "edit" -const PermissionMemberActionTypeReason = "reason" +import ( + "strings" + + "github.com/answerdev/answer/internal/base/validator" +) // PermissionMemberAction permission member action type PermissionMemberAction struct { @@ -9,3 +12,16 @@ type PermissionMemberAction struct { Name string `json:"name"` Type string `json:"type"` } + +// GetPermissionReq get permission request +type GetPermissionReq struct { + Action string `form:"action"` + Actions []string `validate:"omitempty" form:"actions"` +} + +func (r *GetPermissionReq) Check() (errField []*validator.FormErrorField, err error) { + if len(r.Action) > 0 { + r.Actions = strings.Split(r.Action, ",") + } + return nil, nil +} From c7a7cc711ed0d026fdf793807541008f5efd4210 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Thu, 25 May 2023 17:00:03 +0800 Subject: [PATCH 2/4] fix(gravatar): replace when set gravatar url --- internal/schema/user_schema.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go index cf1ade2b..79442cb2 100644 --- a/internal/schema/user_schema.go +++ b/internal/schema/user_schema.go @@ -100,13 +100,15 @@ func (r *GetUserToSetShowResp) GetFromUserEntity(userInfo *entity.User) { if ok { r.Status = statusShow } + avatarInfo := &AvatarInfo{} _ = json.Unmarshal([]byte(userInfo.Avatar), avatarInfo) - if constant.DefaultAvatar == "gravatar" && avatarInfo.Type == "" { - avatarInfo.Type = "gravatar" + if len(avatarInfo.Type) == 0 && constant.DefaultAvatar == AvatarTypeGravatar { + avatarInfo.Type = AvatarTypeGravatar + avatarInfo.Gravatar = gravatar.GetAvatarURL(userInfo.EMail) + } else if avatarInfo.Type == AvatarTypeGravatar { avatarInfo.Gravatar = gravatar.GetAvatarURL(userInfo.EMail) } - // if json.Unmarshal Error avatarInfo.Type is Empty r.Avatar = avatarInfo } @@ -118,7 +120,7 @@ const ( func FormatAvatarInfo(avatarJson, email string) (res string) { defer func() { - if constant.DefaultAvatar == "gravatar" && len(res) == 0 { + if constant.DefaultAvatar == AvatarTypeGravatar && len(res) == 0 { res = gravatar.GetAvatarURL(email) } }() @@ -133,7 +135,7 @@ func FormatAvatarInfo(avatarJson, email string) (res string) { } switch avatarInfo.Type { case AvatarTypeGravatar: - return avatarInfo.Gravatar + return gravatar.GetAvatarURL(email) case AvatarTypeCustom: return avatarInfo.Custom default: From 850637428cc84c4670bddfbbdd751567e3669844 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Thu, 25 May 2023 18:03:10 +0800 Subject: [PATCH 3/4] feat(permission): update translation for no enough reputation to operate --- i18n/en_US.yaml | 4 ++- i18n/zh_CN.yaml | 4 ++- internal/base/reason/reason.go | 1 + internal/controller/permission_controller.go | 10 ++++-- internal/controller/vote_controller.go | 16 ++++----- internal/schema/permission.go | 27 ++++++++++++++++ internal/service/rank/rank_service.go | 34 ++++++++++++-------- 7 files changed, 68 insertions(+), 28 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index c619234d..663554d7 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -184,7 +184,9 @@ backend: fail_to_meet_the_condition: other: Rank fail to meet the condition. vote_fail_to_meet_the_condition: - other: Thanks for the feedback. You need at least {{ rank }} reputation to cast a vote. + other: Thanks for the feedback. You need at least {{.Rank}} reputation to cast a vote. + no_enough_rank_to_operate: + other: You need at least {{.Rank}} reputation to do this. report: handle_failed: other: Report handle failed. diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 6329718e..1987e6e0 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -178,7 +178,9 @@ backend: fail_to_meet_the_condition: other: 级别不符合条件 vote_fail_to_meet_the_condition: - other: 感谢您的投票。您至少需要{{ rank }}声望才能投票。 + other: 感谢您的投票。您至少需要{{.Rank}}声望才能投票。 + no_enough_rank_to_operate: + other: 您至少需要{{.Rank}}声望才能执行此操作。 report: handle_failed: other: 报告处理失败 diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go index 3c257944..b4cdbcb9 100644 --- a/internal/base/reason/reason.go +++ b/internal/base/reason/reason.go @@ -52,6 +52,7 @@ const ( TagAlreadyExist = "error.tag.already_exist" RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition" VoteRankFailToMeetTheCondition = "error.rank.vote_fail_to_meet_the_condition" + NoEnoughRankToOperate = "error.rank.no_enough_rank_to_operate" ThemeNotFound = "error.theme.not_found" LangNotFound = "error.lang.not_found" ReportHandleFailed = "error.report.handle_failed" diff --git a/internal/controller/permission_controller.go b/internal/controller/permission_controller.go index 5dc6a46b..351c90c0 100644 --- a/internal/controller/permission_controller.go +++ b/internal/controller/permission_controller.go @@ -34,14 +34,18 @@ func (u *PermissionController) GetPermission(ctx *gin.Context) { } userID := middleware.GetLoginUserIDFromContext(ctx) - resp, err := u.rankService.CheckOperationPermissions(ctx, userID, req.Actions) + ops, requireRanks, err := u.rankService.CheckOperationPermissionsForRanks(ctx, userID, req.Actions) if err != nil { handler.HandleResponse(ctx, err, nil) return } - mapping := make(map[string]bool, len(resp)) + + lang := handler.GetLangByCtx(ctx) + mapping := make(map[string]*schema.GetPermissionResp, len(ops)) for i, action := range req.Actions { - mapping[action] = resp[i] + t := &schema.GetPermissionResp{HasPermission: ops[i]} + t.TrTip(lang, requireRanks[i]) + mapping[action] = t } handler.HandleResponse(ctx, err, mapping) } diff --git a/internal/controller/vote_controller.go b/internal/controller/vote_controller.go index 46fe8e32..6af80e35 100644 --- a/internal/controller/vote_controller.go +++ b/internal/controller/vote_controller.go @@ -1,8 +1,6 @@ package controller import ( - "fmt" - "github.com/answerdev/answer/internal/base/handler" "github.com/answerdev/answer/internal/base/middleware" "github.com/answerdev/answer/internal/base/reason" @@ -44,16 +42,15 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) { } req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) - can, rank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, true) + can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, true) if err != nil { handler.HandleResponse(ctx, err, nil) return } if !can { lang := handler.GetLang(ctx) - msg := translator.Tr(lang, reason.VoteRankFailToMeetTheCondition) - msg = handler.MsgWithParameter(msg, map[string]string{"rank": fmt.Sprintf("%d", rank)}) - handler.HandleResponse(ctx, errors.Forbidden(reason.VoteRankFailToMeetTheCondition).WithMsg(msg), nil) + msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: needRank}) + handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil) return } @@ -84,16 +81,15 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) { } req.ObjectID = uid.DeShortID(req.ObjectID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) - can, rank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, false) + can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, false) if err != nil { handler.HandleResponse(ctx, err, nil) return } if !can { lang := handler.GetLang(ctx) - msg := translator.Tr(lang, reason.VoteRankFailToMeetTheCondition) - msg = handler.MsgWithParameter(msg, map[string]string{"rank": fmt.Sprintf("%d", rank)}) - handler.HandleResponse(ctx, errors.Forbidden(reason.VoteRankFailToMeetTheCondition).WithMsg(msg), nil) + msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: needRank}) + handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil) return } diff --git a/internal/schema/permission.go b/internal/schema/permission.go index 91f4a0f9..d6fa98f7 100644 --- a/internal/schema/permission.go +++ b/internal/schema/permission.go @@ -3,9 +3,17 @@ package schema import ( "strings" + "github.com/answerdev/answer/internal/base/reason" + "github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/validator" + "github.com/segmentfault/pacman/i18n" ) +// PermissionTrTplData template data as for translate permission message +type PermissionTrTplData struct { + Rank int +} + // PermissionMemberAction permission member action type PermissionMemberAction struct { Action string `json:"action"` @@ -25,3 +33,22 @@ func (r *GetPermissionReq) Check() (errField []*validator.FormErrorField, err er } return nil, nil } + +// GetPermissionResp get permission response +type GetPermissionResp struct { + HasPermission bool `json:"has_permission"` + // only not allow, will return this tip + NoPermissionTip string `json:"no_permission_tip"` +} + +func (r *GetPermissionResp) TrTip(lang i18n.Language, requireRank int) { + if r.HasPermission { + return + } + if requireRank <= 0 { + r.NoPermissionTip = translator.Tr(lang, reason.RankFailToMeetTheCondition) + } else { + r.NoPermissionTip = translator.TrWithData( + lang, reason.NoEnoughRankToOperate, &PermissionTrTplData{Rank: requireRank}) + } +} diff --git a/internal/service/rank/rank_service.go b/internal/service/rank/rank_service.go index 4ef63d00..af60152b 100644 --- a/internal/service/rank/rank_service.go +++ b/internal/service/rank/rank_service.go @@ -95,21 +95,22 @@ func (rs *RankService) CheckOperationPermission(ctx context.Context, userID stri return can, nil } -// CheckOperationPermissions verify that the user has permission -func (rs *RankService) CheckOperationPermissions(ctx context.Context, userID string, actions []string) ( - can []bool, err error) { +// CheckOperationPermissionsForRanks verify that the user has permission +func (rs *RankService) CheckOperationPermissionsForRanks(ctx context.Context, userID string, actions []string) ( + can []bool, requireRanks []int, err error) { can = make([]bool, len(actions)) + requireRanks = make([]int, len(actions)) if len(userID) == 0 { - return can, nil + return can, requireRanks, nil } // get the rank of the current user userInfo, exist, err := rs.userCommon.GetUserBasicInfoByID(ctx, userID) if err != nil { - return can, err + return can, requireRanks, err } if !exist { - return can, nil + return can, requireRanks, nil } powerMapping := rs.getUserPowerMapping(ctx, userID) @@ -118,10 +119,18 @@ func (rs *RankService) CheckOperationPermissions(ctx context.Context, userID str can[idx] = true continue } - meetRank, _ := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action) + meetRank, requireRank := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action) can[idx] = meetRank + requireRanks[idx] = requireRank } - return can, nil + return can, requireRanks, nil +} + +// CheckOperationPermissions verify that the user has permission +func (rs *RankService) CheckOperationPermissions(ctx context.Context, userID string, actions []string) ( + can []bool, err error) { + can, _, err = rs.CheckOperationPermissionsForRanks(ctx, userID, actions) + return can, err } // CheckOperationObjectOwner check operation object owner @@ -142,7 +151,7 @@ func (rs *RankService) CheckOperationObjectOwner(ctx context.Context, userID, ob // CheckVotePermission verify that the user has vote permission func (rs *RankService) CheckVotePermission(ctx context.Context, userID, objectID string, voteUp bool) ( - can bool, rank int, err error) { + can bool, needRank int, err error) { if len(userID) == 0 || len(objectID) == 0 { return false, 0, nil } @@ -180,13 +189,12 @@ func (rs *RankService) CheckVotePermission(ctx context.Context, userID, objectID action = permission.CommentVoteDown } } - meetRank, rank := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action) powerMapping := rs.getUserPowerMapping(ctx, userID) if powerMapping[action] { - return true, rank, nil + return true, 0, nil } - - return meetRank, rank, nil + can, needRank = rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action) + return can, needRank, nil } // getUserPowerMapping get user power mapping From fc94d667b7c820b3cf455a61eb3e7295bd230343 Mon Sep 17 00:00:00 2001 From: aichy126 <16996097+aichy126@users.noreply.github.com> Date: Thu, 25 May 2023 18:12:32 +0800 Subject: [PATCH 4/4] update no_enough_rank_to_operate --- internal/controller/question_controller.go | 35 ++++++++++++++++++++-- internal/schema/question_schema.go | 1 + internal/service/question_service.go | 5 ++++ internal/service/tag_common/tag_common.go | 26 ++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go index b7e254a9..a0c35be6 100644 --- a/internal/controller/question_controller.go +++ b/internal/controller/question_controller.go @@ -5,6 +5,7 @@ import ( "github.com/answerdev/answer/internal/base/middleware" "github.com/answerdev/answer/internal/base/pager" "github.com/answerdev/answer/internal/base/reason" + "github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/validator" "github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/schema" @@ -306,13 +307,14 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) { } req.UserID = middleware.GetLoginUserIDFromContext(ctx) - canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ + canList, requireRanks, err := qc.rankService.CheckOperationPermissionsForRanks(ctx, req.UserID, []string{ permission.QuestionAdd, permission.QuestionEdit, permission.QuestionDelete, permission.QuestionClose, permission.QuestionReopen, permission.TagUseReservedTag, + permission.TagAdd, }) if err != nil { handler.HandleResponse(ctx, err, nil) @@ -324,11 +326,25 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) { req.CanClose = canList[3] req.CanReopen = canList[4] req.CanUseReservedTag = canList[5] + req.CanAddTag = canList[6] if !req.CanAdd { handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil) return } + // can add tag + hasNewTag, err := qc.questionService.HasNewTag(ctx, req.Tags) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !req.CanAddTag && hasNewTag { + lang := handler.GetLang(ctx) + msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: requireRanks[6]}) + handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil) + return + } + errList, err := qc.questionService.CheckAddQuestion(ctx, req) if err != nil { errlist, ok := errList.([]*validator.FormErrorField) @@ -480,11 +496,12 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) { req.ID = uid.DeShortID(req.ID) req.UserID = middleware.GetLoginUserIDFromContext(ctx) - canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ + canList, requireRanks, err := qc.rankService.CheckOperationPermissionsForRanks(ctx, req.UserID, []string{ permission.QuestionEdit, permission.QuestionDelete, permission.QuestionEditWithoutReview, permission.TagUseReservedTag, + permission.TagAdd, }) if err != nil { handler.HandleResponse(ctx, err, nil) @@ -496,6 +513,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) { req.CanDelete = canList[1] req.NoNeedReview = canList[2] || objectOwner req.CanUseReservedTag = canList[3] + req.CanAddTag = canList[4] if !req.CanEdit { handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil) return @@ -511,6 +529,19 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) { return } + // can add tag + hasNewTag, err := qc.questionService.HasNewTag(ctx, req.Tags) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !req.CanAddTag && hasNewTag { + lang := handler.GetLang(ctx) + msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: requireRanks[4]}) + handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil) + return + } + resp, err := qc.questionService.UpdateQuestion(ctx, req) if err != nil { handler.HandleResponse(ctx, err, resp) diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index 859dde54..acabc0d2 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -124,6 +124,7 @@ type QuestionPermission struct { CanUseReservedTag bool `json:"-"` // whether user can invite other user to answer this question CanInviteOtherToAnswer bool `json:"-"` + CanAddTag bool `json:"-"` } type CheckCanQuestionUpdate struct { diff --git a/internal/service/question_service.go b/internal/service/question_service.go index 3c738485..b456d473 100644 --- a/internal/service/question_service.go +++ b/internal/service/question_service.go @@ -200,6 +200,11 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que return nil, nil } +// HasNewTag +func (qs *QuestionService) HasNewTag(ctx context.Context, tags []*schema.TagItem) (bool, error) { + return qs.tagCommon.HasNewTag(ctx, tags) +} + // AddQuestion add question func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo any, err error) { if len(req.Tags) == 0 { diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 41350b14..7332ab80 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -247,6 +247,32 @@ func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.T return false, nil } +func (ts *TagCommonService) HasNewTag(ctx context.Context, tags []*schema.TagItem) (bool, error) { + tagNames := make([]string, 0) + tagMap := make(map[string]bool) + for _, item := range tags { + item.SlugName = strings.ReplaceAll(item.SlugName, " ", "-") + tagNames = append(tagNames, item.SlugName) + tagMap[item.SlugName] = false + } + list, err := ts.GetTagListByNames(ctx, tagNames) + if err != nil { + return true, err + } + for _, item := range list { + _, ok := tagMap[item.SlugName] + if ok { + tagMap[item.SlugName] = true + } + } + for _, has := range tagMap { + if !has { + return true, nil + } + } + return false, nil +} + // GetObjectTag get object tag func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (objTags []*schema.TagResp, err error) { tagsInfoList, err := ts.GetObjectEntityTag(ctx, objectId)