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"
SiteTypeCustomCssHTML = "css-html"
SiteTypeTheme = "theme"
SiteTypePrivileges = "privileges"
)
func ExistInPathIgnore(name string) bool {

View File

@ -1,5 +1,11 @@
package constant
type Privilege struct {
Label string `json:"label"`
Value int `json:"value"`
Key string `json:"-"`
}
const (
RankQuestionAddKey = "rank.question.add"
RankQuestionEditKey = "rank.question.edit"
@ -35,39 +41,80 @@ const (
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 (
RankAllKeys = []string{
RankQuestionAddKey,
RankQuestionEditKey,
RankQuestionDeleteKey,
RankQuestionVoteUpKey,
RankQuestionVoteDownKey,
RankAnswerAddKey,
RankAnswerEditKey,
RankAnswerDeleteKey,
RankAnswerAcceptKey,
RankAnswerVoteUpKey,
RankAnswerVoteDownKey,
RankCommentAddKey,
RankCommentEditKey,
RankCommentDeleteKey,
RankReportAddKey,
RankTagAddKey,
RankTagEditKey,
RankTagDeleteKey,
RankTagSynonymKey,
RankLinkUrlLimitKey,
RankVoteDetailKey,
RankCommentVoteUpKey,
RankCommentVoteDownKey,
RankQuestionEditWithoutReviewKey,
RankAnswerEditWithoutReviewKey,
RankTagEditWithoutReviewKey,
RankAnswerAuditKey,
RankQuestionAuditKey,
RankTagAuditKey,
RankQuestionCloseKey,
RankQuestionReopenKey,
RankTagUseReservedTagKey,
RankAllPrivileges = []*Privilege{
{Label: RankQuestionAddLabel, Key: RankQuestionAddKey},
{Label: RankAnswerAddLabel, Key: RankAnswerAddKey},
{Label: RankCommentAddLabel, Key: RankCommentAddKey},
{Label: RankAnswerAcceptLabel, Key: RankAnswerAcceptKey},
{Label: RankReportAddLabel, Key: RankReportAddKey},
{Label: RankCommentVoteUpLabel, Key: RankCommentVoteUpKey},
{Label: RankLinkUrlLimitLabel, Key: RankLinkUrlLimitKey},
{Label: RankQuestionVoteUpLabel, Key: RankQuestionVoteUpKey},
{Label: RankAnswerVoteUpLabel, Key: RankAnswerVoteUpKey},
{Label: RankQuestionVoteDownLabel, Key: RankQuestionVoteDownKey},
{Label: RankAnswerVoteDownLabel, Key: RankAnswerVoteDownKey},
{Label: RankTagAddLabel, Key: RankTagAddKey},
{Label: RankTagEditLabel, Key: RankTagEditKey},
{Label: RankQuestionEditLabel, Key: RankQuestionEditKey},
{Label: RankAnswerEditLabel, Key: RankAnswerEditKey},
{Label: RankQuestionEditWithoutReviewLabel, Key: RankQuestionEditWithoutReviewKey},
{Label: RankAnswerEditWithoutReviewLabel, Key: RankAnswerEditWithoutReviewKey},
{Label: RankQuestionAuditLabel, Key: RankQuestionAuditKey},
{Label: RankAnswerAuditLabel, Key: RankAnswerAuditKey},
{Label: RankTagAuditLabel, Key: RankTagAuditKey},
{Label: RankTagEditWithoutReviewLabel, Key: RankTagEditWithoutReviewKey},
{Label: RankTagSynonymLabel, Key: RankTagSynonymKey},
}
)

View File

@ -386,9 +386,9 @@ func (sc *SiteInfoController) GetPrivilegesConfig(ctx *gin.Context) {
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Param data body schema.UpdatePrivilegesConfigReq true "smtp config"
// @Param data body schema.UpdatePrivilegesConfigReq true "config"
// @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) {
req := &schema.UpdatePrivilegesConfigReq{}
if handler.BindAndCheck(ctx, req) {

View File

@ -6,6 +6,7 @@ import (
"net/mail"
"net/url"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator"
@ -234,12 +235,78 @@ type GetManifestJsonResp struct {
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 {
Privileges map[string]int `json:"privileges"`
Options []*PrivilegeOption `json:"options"`
SelectedLevel PrivilegeLevel `json:"selected_level"`
}
// UpdatePrivilegesConfigReq
type UpdatePrivilegesConfigReq struct {
Privileges map[string]int `json:"privileges"`
// PrivilegeOption privilege option
type PrivilegeOption struct {
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) {
resp = &schema.GetPrivilegesConfigResp{
Privileges: make(map[string]int, 0),
privilege := &schema.UpdatePrivilegesConfigReq{}
if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypePrivileges, privilege); err != nil {
return nil, err
}
for _, key := range constant.RankAllKeys {
v, err := s.configRepo.GetInt(key)
if err != nil {
log.Error(err)
continue
}
resp.Privileges[key] = v
resp = &schema.GetPrivilegesConfigResp{
Options: schema.DefaultPrivilegeOptions,
SelectedLevel: schema.PrivilegeLevel2,
}
if privilege != nil && privilege.Level > 0 {
resp.SelectedLevel = privilege.Level
}
return resp, nil
}
func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) {
for key, value := range req.Privileges {
err = s.configRepo.SetConfig(key, fmt.Sprintf("%d", value))
var chooseOption *schema.PrivilegeOption
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 {
return
return err
}
}
return

View File

@ -43,7 +43,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService
// GetSiteGeneral get site info general
func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
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 resp, nil
@ -52,7 +52,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
// GetSiteInterface get site info interface
func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
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 resp, nil
@ -61,7 +61,7 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch
// GetSiteBranding get site info branding
func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
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 resp, nil
@ -70,7 +70,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche
// GetSiteWrite get site info write
func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
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 resp, nil
@ -79,7 +79,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.
// GetSiteLegal get site info write
func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
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 resp, nil
@ -88,7 +88,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.
// GetSiteLogin get site login config
func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) {
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 resp, nil
@ -97,7 +97,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.
// GetSiteCustomCssHTML get site custom css html config
func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) {
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 resp, nil
@ -108,7 +108,7 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
resp = &schema.SiteThemeResp{
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
}
resp.TrTheme(ctx)
@ -118,13 +118,13 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
// GetSiteSeo get site seo
func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
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 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)
if err != nil {
return err