mirror of https://gitee.com/answerdev/answer.git
Merge remote-tracking branch 'origin/feat/0.6.0/seo' into test
This commit is contained in:
commit
e721ef2375
|
@ -45,6 +45,7 @@ func runApp() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conf.GetPathIgnoreList()
|
||||
app, cleanup, err := initApplication(
|
||||
c.Debug, c.Server, c.Data.Database, c.Data.Cache, c.I18n, c.Swaggerui, c.ServiceConfig, log.GetLogger())
|
||||
if err != nil {
|
||||
|
|
|
@ -4,4 +4,6 @@ import _ "embed"
|
|||
|
||||
//go:embed config.yaml
|
||||
var Config []byte
|
||||
|
||||
//go:embed path_ignore.yaml
|
||||
var PathIgnore []byte
|
||||
|
|
18
docs/docs.go
18
docs/docs.go
|
@ -6400,11 +6400,7 @@ const docTemplate = `{
|
|||
},
|
||||
"user_info": {
|
||||
"description": "user info",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
|
@ -6416,11 +6412,7 @@ const docTemplate = `{
|
|||
"properties": {
|
||||
"object": {
|
||||
"description": "this object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
},
|
||||
"object_type": {
|
||||
"description": "object_type",
|
||||
|
@ -6818,11 +6810,7 @@ const docTemplate = `{
|
|||
"properties": {
|
||||
"avatar": {
|
||||
"description": "avatar",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
},
|
||||
"bio": {
|
||||
"description": "bio",
|
||||
|
|
|
@ -6388,11 +6388,7 @@
|
|||
},
|
||||
"user_info": {
|
||||
"description": "user info",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
|
@ -6404,11 +6400,7 @@
|
|||
"properties": {
|
||||
"object": {
|
||||
"description": "this object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
},
|
||||
"object_type": {
|
||||
"description": "object_type",
|
||||
|
@ -6806,11 +6798,7 @@
|
|||
"properties": {
|
||||
"avatar": {
|
||||
"description": "avatar",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
},
|
||||
"bio": {
|
||||
"description": "bio",
|
||||
|
|
|
@ -1139,8 +1139,7 @@ definitions:
|
|||
title:
|
||||
type: string
|
||||
user_info:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.UserBasicInfo'
|
||||
$ref: '#/definitions/schema.UserBasicInfo'
|
||||
description: user info
|
||||
vote_count:
|
||||
type: integer
|
||||
|
@ -1148,8 +1147,7 @@ definitions:
|
|||
schema.SearchResp:
|
||||
properties:
|
||||
object:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.SearchObject'
|
||||
$ref: '#/definitions/schema.SearchObject'
|
||||
description: this object
|
||||
object_type:
|
||||
description: object_type
|
||||
|
@ -1427,8 +1425,7 @@ definitions:
|
|||
schema.UpdateInfoRequest:
|
||||
properties:
|
||||
avatar:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.AvatarInfo'
|
||||
$ref: '#/definitions/schema.AvatarInfo'
|
||||
description: avatar
|
||||
bio:
|
||||
description: bio
|
||||
|
|
|
@ -109,6 +109,8 @@ backend:
|
|||
other: "Should not contain synonym tags."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
cannot_set_synonym_as_itself:
|
||||
other: "You cannot set the synonym of the current tag as itself."
|
||||
theme:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
|
|
@ -97,6 +97,8 @@ backend:
|
|||
other: "不应包含同义词标签。"
|
||||
cannot_update:
|
||||
other: "没有更新标签权限。"
|
||||
cannot_set_synonym_as_itself:
|
||||
other: "你无法将当前标签的同义词设置为当前标签自己"
|
||||
theme:
|
||||
not_found:
|
||||
other: "主题未找到"
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"bytes"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/answerdev/answer/configs"
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/server"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
|
@ -12,6 +14,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/service_config"
|
||||
"github.com/answerdev/answer/pkg/writer"
|
||||
"github.com/segmentfault/pacman/contrib/conf/viper"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -25,6 +28,10 @@ type AllConfig struct {
|
|||
Swaggerui *router.SwaggerConfig `json:"swaggerui" mapstructure:"swaggerui" yaml:"swaggerui"`
|
||||
}
|
||||
|
||||
type PathIgnore struct {
|
||||
Users []string `yaml:"users"`
|
||||
}
|
||||
|
||||
// Server server config
|
||||
type Server struct {
|
||||
HTTP *server.HTTP `json:"http" mapstructure:"http" yaml:"http"`
|
||||
|
@ -62,3 +69,18 @@ func RewriteConfig(configFilePath string, allConfig *AllConfig) error {
|
|||
}
|
||||
return writer.ReplaceFile(configFilePath, buf.String())
|
||||
}
|
||||
|
||||
func GetPathIgnoreList() map[string]bool {
|
||||
list := make(map[string]bool, 0)
|
||||
data := &PathIgnore{}
|
||||
err := yaml.Unmarshal(configs.PathIgnore, data)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return list
|
||||
}
|
||||
for _, item := range data.Users {
|
||||
list[item] = true
|
||||
}
|
||||
constant.PathIgnoreMap = list
|
||||
return list
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ const (
|
|||
var (
|
||||
Version string = ""
|
||||
|
||||
PathIgnoreMap map[string]bool
|
||||
|
||||
ObjectTypeStrMapping = map[string]int{
|
||||
QuestionObjectType: 1,
|
||||
AnswerObjectType: 2,
|
||||
|
@ -59,3 +61,11 @@ const (
|
|||
SiteTypeLegal = "legal"
|
||||
SiteTypeSeo = "seo"
|
||||
)
|
||||
|
||||
func ExistInPathIgnore(name string) bool {
|
||||
_, ok := PathIgnoreMap[name]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -58,4 +58,5 @@ const (
|
|||
RevisionReviewUnderway = "error.revision.review_underway"
|
||||
RevisionNoPermission = "error.revision.no_permission"
|
||||
UserCannotUpdateYourRole = "error.user.cannot_update_your_role"
|
||||
TagCannotSetSynonymAsItself = "error.tag.cannot_set_synonym_as_itself"
|
||||
)
|
||||
|
|
|
@ -296,6 +296,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
|||
permission.QuestionEdit,
|
||||
permission.QuestionDelete,
|
||||
permission.QuestionEditWithoutReview,
|
||||
permission.TagUseReservedTag,
|
||||
}, req.ID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
|
@ -304,16 +305,18 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
|||
req.CanEdit = canList[0]
|
||||
req.CanDelete = canList[1]
|
||||
req.NoNeedReview = canList[2]
|
||||
|
||||
req.CanClose = middleware.GetIsAdminFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
req.CanUseReservedTag = canList[3]
|
||||
if !req.CanEdit {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = qc.questionService.UpdateQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, &schema.UpdateQuestionResp{WaitForReview: !req.NoNeedReview})
|
||||
resp, err := qc.questionService.UpdateQuestion(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, &schema.UpdateQuestionResp{WaitForReview: !req.NoNeedReview})
|
||||
}
|
||||
|
||||
// CloseMsgList close question msg list
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
templaterender "github.com/answerdev/answer/internal/controller/template_render"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
|
@ -265,15 +266,23 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
|
|||
|
||||
// UserInfo user info
|
||||
func (tc *TemplateController) UserInfo(ctx *gin.Context) {
|
||||
// urlPath := ctx.Request.URL.Path
|
||||
// filePath := ""
|
||||
// switch urlPath {
|
||||
// case "/users/login":
|
||||
// filePath = "build/index.html"
|
||||
// case "/users/register":
|
||||
// filePath = "build/index.html"
|
||||
// default:
|
||||
username := ctx.Param("username")
|
||||
if username == "" {
|
||||
tc.Page404(ctx)
|
||||
return
|
||||
}
|
||||
exist := constant.ExistInPathIgnore(username)
|
||||
if exist {
|
||||
file, err := ui.Build.ReadFile("build/index.html")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
tc.Page404(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Header("content-type", "text/html;charset=utf-8")
|
||||
ctx.String(http.StatusOK, string(file))
|
||||
return
|
||||
}
|
||||
req := &schema.GetOtherUserInfoByUsernameReq{}
|
||||
req.Username = username
|
||||
userinfo, err := tc.templateRenderController.UserInfo(ctx, req)
|
||||
|
@ -292,16 +301,6 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
|
|||
"userinfo": userinfo,
|
||||
"bio": template.HTML(userinfo.Info.BioHTML),
|
||||
})
|
||||
// }
|
||||
|
||||
// file, err := ui.Build.ReadFile(filePath)
|
||||
// if err != nil {
|
||||
// log.Error(err)
|
||||
// ctx.Status(http.StatusNotFound)
|
||||
// return
|
||||
// }
|
||||
// ctx.Header("content-type", "text/html;charset=utf-8")
|
||||
// ctx.String(http.StatusOK, string(file))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
{RoleID: 2, PowerType: permission.AnswerAudit},
|
||||
{RoleID: 2, PowerType: permission.QuestionAudit},
|
||||
{RoleID: 2, PowerType: permission.TagAudit},
|
||||
{RoleID: 2, PowerType: permission.TagUseReservedTag},
|
||||
|
||||
{RoleID: 3, PowerType: permission.QuestionAdd},
|
||||
{RoleID: 3, PowerType: permission.QuestionEdit},
|
||||
|
@ -154,6 +155,7 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
{RoleID: 3, PowerType: permission.AnswerAudit},
|
||||
{RoleID: 3, PowerType: permission.QuestionAudit},
|
||||
{RoleID: 3, PowerType: permission.TagAudit},
|
||||
{RoleID: 3, PowerType: permission.TagUseReservedTag},
|
||||
}
|
||||
|
||||
// insert default powers
|
||||
|
@ -190,6 +192,7 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 115, Key: "rank.question.close", Value: `-1`},
|
||||
{ID: 116, Key: "rank.question.reopen", Value: `-1`},
|
||||
{ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
|
|
|
@ -41,6 +41,6 @@ func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
|
|||
|
||||
r.GET("/tags", a.templateController.TagList)
|
||||
r.GET("/tags/:tag", a.templateController.TagInfo)
|
||||
// r.GET("/users/:username", a.templateController.UserInfo)
|
||||
r.GET("/users/:username", a.templateController.UserInfo)
|
||||
r.GET("/404", a.templateController.Page404)
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ type QuestionPermission struct {
|
|||
CanClose bool `json:"-"`
|
||||
// whether user can reopen it
|
||||
CanReopen bool `json:"-"`
|
||||
// whether user can use reserved it
|
||||
CanUseReservedTag bool `json:"-"`
|
||||
}
|
||||
|
||||
type CheckCanQuestionUpdate struct {
|
||||
|
@ -76,7 +78,6 @@ type QuestionUpdate struct {
|
|||
EditSummary string `validate:"omitempty" json:"edit_summary"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
IsAdmin bool `json:"-"`
|
||||
NoNeedReview bool `json:"-"`
|
||||
QuestionPermission
|
||||
}
|
||||
|
|
|
@ -34,4 +34,5 @@ const (
|
|||
AnswerAudit = "answer.audit"
|
||||
QuestionAudit = "question.audit"
|
||||
TagAudit = "tag.audit"
|
||||
TagUseReservedTag = "tag.use_reserved_tag"
|
||||
)
|
||||
|
|
|
@ -352,14 +352,23 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
|||
return questionInfo, tagerr
|
||||
}
|
||||
|
||||
// If it's not admin
|
||||
if !req.IsAdmin {
|
||||
//CheckChangeTag
|
||||
|
||||
CheckTag, CheckTaglist := qs.CheckChangeReservedTag(ctx, oldTags, Tags)
|
||||
if !CheckTag {
|
||||
// if user can not use reserved tag, old reserved tag can not be removed and new reserved tag can not be added.
|
||||
if !req.CanUseReservedTag {
|
||||
CheckOldTag, CheckNewTag, CheckOldTaglist, CheckNewTaglist := qs.CheckChangeReservedTag(ctx, oldTags, Tags)
|
||||
if !CheckOldTag {
|
||||
errMsg := fmt.Sprintf(`The reserved tag "%s" must be present.`,
|
||||
strings.Join(CheckTaglist, ","))
|
||||
strings.Join(CheckOldTaglist, ","))
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: errMsg,
|
||||
})
|
||||
err = errors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
|
||||
return errorlist, err
|
||||
}
|
||||
if !CheckNewTag {
|
||||
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
|
||||
strings.Join(CheckNewTaglist, ","))
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
|
@ -393,7 +402,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
|||
Log: req.EditSummary,
|
||||
}
|
||||
|
||||
if req.NoNeedReview || req.IsAdmin || dbinfo.UserID == req.UserID {
|
||||
if req.NoNeedReview {
|
||||
canUpdate = true
|
||||
}
|
||||
|
||||
|
@ -476,7 +485,7 @@ func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.
|
|||
return qs.tagCommon.ObjectChangeTag(ctx, objectTagData)
|
||||
}
|
||||
|
||||
func (qs *QuestionService) CheckChangeReservedTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, []string) {
|
||||
func (qs *QuestionService) CheckChangeReservedTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, bool, []string, []string) {
|
||||
return qs.tagCommon.CheckChangeReservedTag(ctx, oldobjectTagData, objectTagData)
|
||||
}
|
||||
|
||||
|
|
|
@ -150,16 +150,10 @@ func (rs *RankService) CheckVotePermission(ctx context.Context, userID, objectID
|
|||
if !exist {
|
||||
return can, nil
|
||||
}
|
||||
// TODO administrator have all permissions
|
||||
//if userInfo.IsAdmin {
|
||||
// return true, nil
|
||||
//}
|
||||
|
||||
objectInfo, err := rs.objectInfoService.GetInfo(ctx, objectID)
|
||||
if err != nil {
|
||||
return can, err
|
||||
}
|
||||
|
||||
action := ""
|
||||
switch objectInfo.ObjectType {
|
||||
case constant.QuestionObjectType:
|
||||
|
@ -181,6 +175,11 @@ func (rs *RankService) CheckVotePermission(ctx context.Context, userID, objectID
|
|||
action = permission.CommentVoteDown
|
||||
}
|
||||
}
|
||||
powerMapping := rs.getUserPowerMapping(ctx, userID)
|
||||
if powerMapping[action] {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
meetRank := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
return meetRank, nil
|
||||
}
|
||||
|
|
|
@ -216,6 +216,9 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
|
|||
|
||||
// find all exist tag
|
||||
for _, item := range req.SynonymTagList {
|
||||
if item.SlugName == mainTagInfo.SlugName {
|
||||
return errors.BadRequest(reason.TagCannotSetSynonymAsItself)
|
||||
}
|
||||
addSynonymTagList = append(addSynonymTagList, item.SlugName)
|
||||
}
|
||||
tagListInDB, err := ts.tagCommonService.GetTagListByNames(ctx, addSynonymTagList)
|
||||
|
|
|
@ -443,9 +443,10 @@ func (ts *TagCommonService) CheckTagsIsChange(ctx context.Context, tagNameList,
|
|||
return false
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) CheckChangeReservedTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, []string) {
|
||||
func (ts *TagCommonService) CheckChangeReservedTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, bool, []string, []string) {
|
||||
reservedTagsMap := make(map[string]bool)
|
||||
needTagsMap := make([]string, 0)
|
||||
notNeedTagsMap := make([]string, 0)
|
||||
for _, tag := range objectTagData {
|
||||
if tag.Reserved {
|
||||
reservedTagsMap[tag.SlugName] = true
|
||||
|
@ -456,14 +457,27 @@ func (ts *TagCommonService) CheckChangeReservedTag(ctx context.Context, oldobjec
|
|||
_, ok := reservedTagsMap[tag.SlugName]
|
||||
if !ok {
|
||||
needTagsMap = append(needTagsMap, tag.SlugName)
|
||||
} else {
|
||||
reservedTagsMap[tag.SlugName] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(needTagsMap) > 0 {
|
||||
return false, needTagsMap
|
||||
|
||||
for k, v := range reservedTagsMap {
|
||||
if v {
|
||||
notNeedTagsMap = append(notNeedTagsMap, k)
|
||||
}
|
||||
}
|
||||
|
||||
return true, []string{}
|
||||
if len(needTagsMap) > 0 {
|
||||
return false, true, needTagsMap, []string{}
|
||||
}
|
||||
|
||||
if len(notNeedTagsMap) > 0 {
|
||||
return true, false, []string{}, notNeedTagsMap
|
||||
}
|
||||
|
||||
return true, true, []string{}, []string{}
|
||||
}
|
||||
|
||||
// ObjectChangeTag change object tag list
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{{template "header" . }}
|
||||
{{translator $.language "error.object.old_password_verification_failed"}}
|
||||
<div class="pt-4 mt-2 mb-5 container">
|
||||
<div class="justify-content-center row">
|
||||
<div class="col-xxl-7 col-lg-8 col-sm-12">
|
||||
|
|
Loading…
Reference in New Issue