feat(permission): add privileges API

This commit is contained in:
LinkinStars 2023-04-12 10:35:40 +08:00
parent 1c235dbd9b
commit 4cfa0cd8fe
6 changed files with 201 additions and 62 deletions

View File

@ -65,6 +65,7 @@ const (
SiteTypeLogin = "login" SiteTypeLogin = "login"
SiteTypeCustomCssHTML = "css-html" SiteTypeCustomCssHTML = "css-html"
SiteTypeTheme = "theme" SiteTypeTheme = "theme"
SiteTypePrivileges = "privileges"
) )
func ExistInPathIgnore(name string) bool { func ExistInPathIgnore(name string) bool {

View File

@ -1,5 +1,11 @@
package constant package constant
type Privilege struct {
Label string `json:"label"`
Value int `json:"value"`
Key string `json:"-"`
}
const ( const (
RankQuestionAddKey = "rank.question.add" RankQuestionAddKey = "rank.question.add"
RankQuestionEditKey = "rank.question.edit" RankQuestionEditKey = "rank.question.edit"
@ -35,39 +41,80 @@ const (
RankTagUseReservedTagKey = "rank.tag.use_reserved_tag" RankTagUseReservedTagKey = "rank.tag.use_reserved_tag"
) )
//| Permission | Level 1 | Level 2 | Level 3 | Custom Level |
//| -------------------------------------- | ------------------------------------------------ | --------------------------------------------- | --------------------------------------------- | ------------ |
//| Description | less reputation required for private team, group | low reputation required for startup community | high reputation required for mature community | |
//| Ask question | 1 | 1 | 1 | |
//| Write answer | 1 | 1 | 1 | |
//| Write comment | 1 | 1 | 1 | |
//| Accept answer | 1 | 1 | 1 | |
//| Flag | 1 | 1 | 1 | |
//| Upvote comment | 1 | 1 | 1 | |
//| Post more than 2 links at a time | 1 | 10 | 10 | |
//| Upvote question | 1 | 1 | 15 | |
//| Upvote answer | 1 | 1 | 15 | |
//| Downvote question | 125 | 125 | 125 | |
//| Downvote answer | 125 | 125 | 125 | |
//| Create new tag | 1 | 750 | 1500 | |
//| Edit tag description (need to review) | 1 | 50 | 100 | |
//| Edit other's question (need to review) | 1 | 100 | 200 | |
//| Edit other's answer (need to review) | 1 | 100 | 200 | |
//| Edit other's question without review | 1 | 1000 | 2000 | |
//| Edit other's answer without review | 1 | 1000 | 2000 | |
//| Revew question edits | 1 | 1000 | 2000 | |
//| Review answer edits | 1 | 1000 | 2000 | |
//| Review tag edits | 1 | 2500 | 5000 | |
//| Edit tag description without review | 1 | 10000 | 20000 | |
//| Manage tag synonyms | 1 | 10000 | 20000 | |
const (
RankQuestionAddLabel = "Ask question"
RankAnswerAddLabel = "Write answer"
RankCommentAddLabel = "Write comment"
RankAnswerAcceptLabel = "Accept answer"
RankReportAddLabel = "Flag"
RankCommentVoteUpLabel = "Upvote comment"
RankLinkUrlLimitLabel = "Post more than 2 links at a time"
RankQuestionVoteUpLabel = "Upvote question"
RankAnswerVoteUpLabel = "Upvote answer"
RankQuestionVoteDownLabel = "Downvote question"
RankAnswerVoteDownLabel = "Downvote answer"
RankTagAddLabel = "Create new tag"
RankTagEditLabel = "Edit tag description (need to review)"
RankQuestionEditLabel = "Edit other's question (need to review)"
RankAnswerEditLabel = "Edit other's answer (need to review)"
RankQuestionEditWithoutReviewLabel = "Edit other's question without review"
RankAnswerEditWithoutReviewLabel = "Edit other's answer without review"
RankQuestionAuditLabel = "Review question edits"
RankAnswerAuditLabel = "Review answer edits"
RankTagAuditLabel = "Review tag edits"
RankTagEditWithoutReviewLabel = "Edit tag description without review"
RankTagSynonymLabel = "Manage tag synonyms"
)
var ( var (
RankAllKeys = []string{ RankAllPrivileges = []*Privilege{
RankQuestionAddKey, {Label: RankQuestionAddLabel, Key: RankQuestionAddKey},
RankQuestionEditKey, {Label: RankAnswerAddLabel, Key: RankAnswerAddKey},
RankQuestionDeleteKey, {Label: RankCommentAddLabel, Key: RankCommentAddKey},
RankQuestionVoteUpKey, {Label: RankAnswerAcceptLabel, Key: RankAnswerAcceptKey},
RankQuestionVoteDownKey, {Label: RankReportAddLabel, Key: RankReportAddKey},
RankAnswerAddKey, {Label: RankCommentVoteUpLabel, Key: RankCommentVoteUpKey},
RankAnswerEditKey, {Label: RankLinkUrlLimitLabel, Key: RankLinkUrlLimitKey},
RankAnswerDeleteKey, {Label: RankQuestionVoteUpLabel, Key: RankQuestionVoteUpKey},
RankAnswerAcceptKey, {Label: RankAnswerVoteUpLabel, Key: RankAnswerVoteUpKey},
RankAnswerVoteUpKey, {Label: RankQuestionVoteDownLabel, Key: RankQuestionVoteDownKey},
RankAnswerVoteDownKey, {Label: RankAnswerVoteDownLabel, Key: RankAnswerVoteDownKey},
RankCommentAddKey, {Label: RankTagAddLabel, Key: RankTagAddKey},
RankCommentEditKey, {Label: RankTagEditLabel, Key: RankTagEditKey},
RankCommentDeleteKey, {Label: RankQuestionEditLabel, Key: RankQuestionEditKey},
RankReportAddKey, {Label: RankAnswerEditLabel, Key: RankAnswerEditKey},
RankTagAddKey, {Label: RankQuestionEditWithoutReviewLabel, Key: RankQuestionEditWithoutReviewKey},
RankTagEditKey, {Label: RankAnswerEditWithoutReviewLabel, Key: RankAnswerEditWithoutReviewKey},
RankTagDeleteKey, {Label: RankQuestionAuditLabel, Key: RankQuestionAuditKey},
RankTagSynonymKey, {Label: RankAnswerAuditLabel, Key: RankAnswerAuditKey},
RankLinkUrlLimitKey, {Label: RankTagAuditLabel, Key: RankTagAuditKey},
RankVoteDetailKey, {Label: RankTagEditWithoutReviewLabel, Key: RankTagEditWithoutReviewKey},
RankCommentVoteUpKey, {Label: RankTagSynonymLabel, Key: RankTagSynonymKey},
RankCommentVoteDownKey,
RankQuestionEditWithoutReviewKey,
RankAnswerEditWithoutReviewKey,
RankTagEditWithoutReviewKey,
RankAnswerAuditKey,
RankQuestionAuditKey,
RankTagAuditKey,
RankQuestionCloseKey,
RankQuestionReopenKey,
RankTagUseReservedTagKey,
} }
) )

View File

@ -386,9 +386,9 @@ func (sc *SiteInfoController) GetPrivilegesConfig(ctx *gin.Context) {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Tags admin // @Tags admin
// @Produce json // @Produce json
// @Param data body schema.UpdatePrivilegesConfigReq true "smtp config" // @Param data body schema.UpdatePrivilegesConfigReq true "config"
// @Success 200 {object} handler.RespBody{} // @Success 200 {object} handler.RespBody{}
// @Router /answer/admin/api/setting/smtp [put] // @Router /answer/admin/api/setting/privileges [put]
func (sc *SiteInfoController) UpdatePrivilegesConfig(ctx *gin.Context) { func (sc *SiteInfoController) UpdatePrivilegesConfig(ctx *gin.Context) {
req := &schema.UpdatePrivilegesConfigReq{} req := &schema.UpdatePrivilegesConfigReq{}
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {

View File

@ -6,6 +6,7 @@ import (
"net/mail" "net/mail"
"net/url" "net/url"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler" "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason" "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/translator"
@ -234,12 +235,78 @@ type GetManifestJsonResp struct {
BackgroundColor string `json:"background_color"` BackgroundColor string `json:"background_color"`
} }
// GetPrivilegesConfigResp const (
// PrivilegeLevel1 low
PrivilegeLevel1 PrivilegeLevel = 1
// PrivilegeLevel2 medium
PrivilegeLevel2 PrivilegeLevel = 2
// PrivilegeLevel3 high
PrivilegeLevel3 PrivilegeLevel = 3
)
type PrivilegeLevel int
// GetPrivilegesConfigResp get privileges config response
type GetPrivilegesConfigResp struct { type GetPrivilegesConfigResp struct {
Privileges map[string]int `json:"privileges"` Options []*PrivilegeOption `json:"options"`
SelectedLevel PrivilegeLevel `json:"selected_level"`
} }
// UpdatePrivilegesConfigReq // PrivilegeOption privilege option
type UpdatePrivilegesConfigReq struct { type PrivilegeOption struct {
Privileges map[string]int `json:"privileges"` Level PrivilegeLevel `json:"level"`
Privileges []*constant.Privilege `json:"privileges"`
}
// UpdatePrivilegesConfigReq update privileges config request
type UpdatePrivilegesConfigReq struct {
Level PrivilegeLevel `validate:"required,min=1,max=3" json:"level"`
}
var (
DefaultPrivilegeOptions []*PrivilegeOption
privilegeOptionsLevelMapping = map[string][]int{
constant.RankQuestionAddKey: {1, 1, 1},
constant.RankAnswerAddKey: {1, 1, 1},
constant.RankCommentAddKey: {1, 1, 1},
constant.RankAnswerAcceptKey: {1, 1, 1},
constant.RankReportAddKey: {1, 1, 1},
constant.RankCommentVoteUpKey: {1, 1, 1},
constant.RankLinkUrlLimitKey: {1, 10, 10},
constant.RankQuestionVoteUpKey: {1, 1, 15},
constant.RankAnswerVoteUpKey: {1, 1, 15},
constant.RankQuestionVoteDownKey: {125, 125, 125},
constant.RankAnswerVoteDownKey: {125, 125, 125},
constant.RankTagAddKey: {1, 750, 1500},
constant.RankTagEditKey: {1, 50, 100},
constant.RankQuestionEditKey: {1, 100, 200},
constant.RankAnswerEditKey: {1, 100, 200},
constant.RankQuestionEditWithoutReviewKey: {1, 1000, 2000},
constant.RankAnswerEditWithoutReviewKey: {1, 1000, 2000},
constant.RankQuestionAuditKey: {1, 1000, 2000},
constant.RankAnswerAuditKey: {1, 1000, 2000},
constant.RankTagAuditKey: {1, 2500, 5000},
constant.RankTagEditWithoutReviewKey: {1, 10000, 20000},
constant.RankTagSynonymKey: {1, 10000, 20000},
}
)
func init() {
for _, option := range []PrivilegeLevel{PrivilegeLevel1, PrivilegeLevel2, PrivilegeLevel3} {
op := &PrivilegeOption{
Level: option,
}
for _, privilege := range constant.RankAllPrivileges {
if len(privilegeOptionsLevelMapping[privilege.Key]) == 0 {
fmt.Println("privilege key not found: ", privilege.Key)
continue
}
op.Privileges = append(op.Privileges, &constant.Privilege{
Label: privilege.Label,
Value: privilegeOptionsLevelMapping[privilege.Key][option-1],
Key: privilege.Key,
})
}
DefaultPrivilegeOptions = append(DefaultPrivilegeOptions, op)
}
} }

View File

@ -308,25 +308,49 @@ func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (e
} }
func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) { func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) {
resp = &schema.GetPrivilegesConfigResp{ privilege := &schema.UpdatePrivilegesConfigReq{}
Privileges: make(map[string]int, 0), if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypePrivileges, privilege); err != nil {
return nil, err
} }
for _, key := range constant.RankAllKeys { resp = &schema.GetPrivilegesConfigResp{
v, err := s.configRepo.GetInt(key) Options: schema.DefaultPrivilegeOptions,
if err != nil { SelectedLevel: schema.PrivilegeLevel2,
log.Error(err) }
continue if privilege != nil && privilege.Level > 0 {
} resp.SelectedLevel = privilege.Level
resp.Privileges[key] = v
} }
return resp, nil return resp, nil
} }
func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) { func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) {
for key, value := range req.Privileges { var chooseOption *schema.PrivilegeOption
err = s.configRepo.SetConfig(key, fmt.Sprintf("%d", value)) for _, option := range schema.DefaultPrivilegeOptions {
if option.Level == req.Level {
chooseOption = option
break
}
}
if chooseOption == nil {
return nil
}
// update site info that user choose which privilege level
content, _ := json.Marshal(req)
data := &entity.SiteInfo{
Type: constant.SiteTypePrivileges,
Content: string(content),
Status: 1,
}
err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePrivileges, data)
if err != nil {
return err
}
// update privilege in config
for _, privilege := range chooseOption.Privileges {
err = s.configRepo.SetConfig(privilege.Key, fmt.Sprintf("%d", privilege.Value))
if err != nil { if err != nil {
return return err
} }
} }
return return

View File

@ -43,7 +43,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService
// GetSiteGeneral get site info general // GetSiteGeneral get site info general
func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
resp = &schema.SiteGeneralResp{} resp = &schema.SiteGeneralResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -52,7 +52,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
// GetSiteInterface get site info interface // GetSiteInterface get site info interface
func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
resp = &schema.SiteInterfaceResp{} resp = &schema.SiteInterfaceResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -61,7 +61,7 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch
// GetSiteBranding get site info branding // GetSiteBranding get site info branding
func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
resp = &schema.SiteBrandingResp{} resp = &schema.SiteBrandingResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -70,7 +70,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche
// GetSiteWrite get site info write // GetSiteWrite get site info write
func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
resp = &schema.SiteWriteResp{} resp = &schema.SiteWriteResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -79,7 +79,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.
// GetSiteLegal get site info write // GetSiteLegal get site info write
func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
resp = &schema.SiteLegalResp{} resp = &schema.SiteLegalResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -88,7 +88,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.
// GetSiteLogin get site login config // GetSiteLogin get site login config
func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) { func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) {
resp = &schema.SiteLoginResp{} resp = &schema.SiteLoginResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -97,7 +97,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.
// GetSiteCustomCssHTML get site custom css html config // GetSiteCustomCssHTML get site custom css html config
func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) { func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) {
resp = &schema.SiteCustomCssHTMLResp{} resp = &schema.SiteCustomCssHTMLResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -108,7 +108,7 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
resp = &schema.SiteThemeResp{ resp = &schema.SiteThemeResp{
ThemeOptions: schema.GetThemeOptions, ThemeOptions: schema.GetThemeOptions,
} }
if err = s.getSiteInfoByType(ctx, constant.SiteTypeTheme, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeTheme, resp); err != nil {
return nil, err return nil, err
} }
resp.TrTheme(ctx) resp.TrTheme(ctx)
@ -118,13 +118,13 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
// GetSiteSeo get site seo // GetSiteSeo get site seo
func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) { func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
resp = &schema.SiteSeoReq{} resp = &schema.SiteSeoReq{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
} }
func (s *SiteInfoCommonService) getSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) { func (s *SiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType) siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
if err != nil { if err != nil {
return err return err