Merge remote-tracking branch 'github/feat/1.0.7/tag' into feat/1.0.7/user-avatar

This commit is contained in:
LinkinStars
2023-03-03 17:05:23 +08:00
19 changed files with 319 additions and 72 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData)
questionController := controller.NewQuestionController(questionService, rankService)
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService)
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
+68 -16
View File
@@ -3932,6 +3932,38 @@ const docTemplate = `{
}
}
},
"post": {
"description": "add tag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tag"
],
"summary": "add tag",
"parameters": [
{
"description": "tag",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AddTagReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
},
"delete": {
"description": "delete tag",
"consumes": [
@@ -5545,6 +5577,31 @@ const docTemplate = `{
}
}
},
"schema.AddTagReq": {
"type": "object",
"required": [
"display_name",
"original_text",
"slug_name"
],
"properties": {
"display_name": {
"description": "display_name",
"type": "string",
"maxLength": 35
},
"original_text": {
"description": "original text",
"type": "string",
"maxLength": 65536
},
"slug_name": {
"description": "slug_name",
"type": "string",
"maxLength": 35
}
}
},
"schema.AddUserReq": {
"type": "object",
"required": [
@@ -5937,14 +5994,6 @@ const docTemplate = `{
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"last_login_date": {
"description": "last login date",
"type": "integer"
@@ -6428,10 +6477,6 @@ const docTemplate = `{
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"language": {
"description": "language",
"type": "string"
@@ -6464,6 +6509,10 @@ const docTemplate = `{
"description": "rank",
"type": "integer"
},
"role_id": {
"description": "role id",
"type": "integer"
},
"status": {
"description": "user status",
"type": "string"
@@ -6528,10 +6577,6 @@ const docTemplate = `{
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"language": {
"description": "language",
"type": "string"
@@ -6564,6 +6609,10 @@ const docTemplate = `{
"description": "rank",
"type": "integer"
},
"role_id": {
"description": "role id",
"type": "integer"
},
"status": {
"description": "user status",
"type": "string"
@@ -7173,6 +7222,9 @@ const docTemplate = `{
},
"theme": {
"$ref": "#/definitions/schema.SiteThemeResp"
},
"version": {
"type": "string"
}
}
},
+68 -16
View File
@@ -3920,6 +3920,38 @@
}
}
},
"post": {
"description": "add tag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tag"
],
"summary": "add tag",
"parameters": [
{
"description": "tag",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AddTagReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
},
"delete": {
"description": "delete tag",
"consumes": [
@@ -5533,6 +5565,31 @@
}
}
},
"schema.AddTagReq": {
"type": "object",
"required": [
"display_name",
"original_text",
"slug_name"
],
"properties": {
"display_name": {
"description": "display_name",
"type": "string",
"maxLength": 35
},
"original_text": {
"description": "original text",
"type": "string",
"maxLength": 65536
},
"slug_name": {
"description": "slug_name",
"type": "string",
"maxLength": 35
}
}
},
"schema.AddUserReq": {
"type": "object",
"required": [
@@ -5925,14 +5982,6 @@
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"last_login_date": {
"description": "last login date",
"type": "integer"
@@ -6416,10 +6465,6 @@
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"language": {
"description": "language",
"type": "string"
@@ -6452,6 +6497,10 @@
"description": "rank",
"type": "integer"
},
"role_id": {
"description": "role id",
"type": "integer"
},
"status": {
"description": "user status",
"type": "string"
@@ -6516,10 +6565,6 @@
"description": "ip info",
"type": "string"
},
"is_admin": {
"description": "is admin",
"type": "boolean"
},
"language": {
"description": "language",
"type": "string"
@@ -6552,6 +6597,10 @@
"description": "rank",
"type": "integer"
},
"role_id": {
"description": "role id",
"type": "integer"
},
"status": {
"description": "user status",
"type": "string"
@@ -7161,6 +7210,9 @@
},
"theme": {
"$ref": "#/definitions/schema.SiteThemeResp"
},
"version": {
"type": "string"
}
}
},
+48 -12
View File
@@ -174,6 +174,25 @@ definitions:
- object_id
- report_type
type: object
schema.AddTagReq:
properties:
display_name:
description: display_name
maxLength: 35
type: string
original_text:
description: original text
maxLength: 65536
type: string
slug_name:
description: slug_name
maxLength: 35
type: string
required:
- display_name
- original_text
- slug_name
type: object
schema.AddUserReq:
properties:
display_name:
@@ -454,12 +473,6 @@ definitions:
id:
description: user id
type: string
ip_info:
description: ip info
type: string
is_admin:
description: is admin
type: boolean
last_login_date:
description: last login date
type: integer
@@ -806,9 +819,6 @@ definitions:
ip_info:
description: ip info
type: string
is_admin:
description: is admin
type: boolean
language:
description: language
type: string
@@ -833,6 +843,9 @@ definitions:
rank:
description: rank
type: integer
role_id:
description: role id
type: integer
status:
description: user status
type: string
@@ -880,9 +893,6 @@ definitions:
ip_info:
description: ip info
type: string
is_admin:
description: is admin
type: boolean
language:
description: language
type: string
@@ -907,6 +917,9 @@ definitions:
rank:
description: rank
type: integer
role_id:
description: role id
type: integer
status:
description: user status
type: string
@@ -1335,6 +1348,8 @@ definitions:
$ref: '#/definitions/schema.SiteSeoReq'
theme:
$ref: '#/definitions/schema.SiteThemeResp'
version:
type: string
type: object
schema.SiteInterfaceReq:
properties:
@@ -4284,6 +4299,27 @@ paths:
summary: get tag one
tags:
- Tag
post:
consumes:
- application/json
description: add tag
parameters:
- description: tag
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.AddTagReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.RespBody'
summary: add tag
tags:
- Tag
put:
consumes:
- application/json
+2
View File
@@ -98,6 +98,8 @@ backend:
not_found:
other: Report not found.
tag:
already_exist:
other: Tag already exists.
not_found:
other: Tag not found.
recommend_tag_not_found:
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"strings"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/role"
"github.com/answerdev/answer/internal/service/siteinfo_common"
"github.com/answerdev/answer/internal/base/handler"
@@ -154,7 +155,7 @@ func GetIsAdminFromContext(ctx *gin.Context) (isAdmin bool) {
if userInfo == nil {
return false
}
return userInfo.IsAdmin
return userInfo.RoleID == role.RoleAdminID
}
// GetUserInfoFromContext get user info from context
+1
View File
@@ -44,6 +44,7 @@ const (
TagNotContainSynonym = "error.tag.not_contain_synonym_tags"
TagCannotUpdate = "error.tag.cannot_update"
TagIsUsedCannotDelete = "error.tag.is_used_cannot_delete"
TagAlreadyExist = "error.tag.already_exist"
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
ThemeNotFound = "error.theme.not_found"
LangNotFound = "error.lang.not_found"
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity"
"github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/role"
"github.com/gin-gonic/gin"
)
@@ -40,7 +41,7 @@ func (ac *ActivityController) GetObjectTimeline(ctx *gin.Context) {
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
if userInfo := middleware.GetUserInfoFromContext(ctx); userInfo != nil {
req.IsAdmin = userInfo.IsAdmin
req.IsAdmin = userInfo.RoleID == role.RoleAdminID
}
resp, err := ac.activityService.GetObjectTimeline(ctx, req)
+6 -3
View File
@@ -51,13 +51,16 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
can, err := ac.rankService.CheckOperationPermission(ctx, req.UserID, permission.AnswerDelete, req.ID)
objectOwner := ac.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.AnswerDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !can {
req.CanDelete = canList[0] || objectOwner
if !req.CanDelete {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
+32
View File
@@ -98,6 +98,38 @@ func (tc *TagController) RemoveTag(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil)
}
// AddTag add tag
// @Summary add tag
// @Description add tag
// @Tags Tag
// @Accept json
// @Produce json
// @Param data body schema.AddTagReq true "tag"
// @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/tag [post]
func (tc *TagController) AddTag(ctx *gin.Context) {
req := &schema.AddTagReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, err := tc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.TagAdd,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !canList[0] {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
resp, err := tc.tagCommonService.AddTag(ctx, req)
handler.HandleResponse(ctx, err, resp)
}
// UpdateTag update tag
// @Summary update tag
// @Description update tag
+1 -1
View File
@@ -5,5 +5,5 @@ type UserCacheInfo struct {
UserID string `json:"user_id"`
UserStatus int `json:"user_status"`
EmailStatus int `json:"email_status"`
IsAdmin bool `json:"is_admin"`
RoleID int `json:"role_id"`
}
+1
View File
@@ -175,6 +175,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
// tag
r.GET("/question/tags", a.tagController.SearchTagLike)
r.POST("/tag", a.tagController.AddTag)
r.PUT("/tag", a.tagController.UpdateTag)
r.DELETE("/tag", a.tagController.RemoveTag)
r.PUT("/tag/synonym", a.tagController.UpdateTagSynonym)
+3 -2
View File
@@ -10,8 +10,9 @@ type RemoveAnswerReq struct {
// answer id
ID string `validate:"required" json:"id"`
// user id
UserID string `json:"-"`
IsAdmin bool `json:"-"`
UserID string `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
}
const (
+25
View File
@@ -161,6 +161,31 @@ type RemoveTagReq struct {
UserID string `json:"-"`
}
// AddTagReq add tag request
type AddTagReq struct {
// slug_name
SlugName string `validate:"required,gt=0,lte=35" json:"slug_name"`
// display_name
DisplayName string `validate:"required,gt=0,lte=35" json:"display_name"`
// original text
OriginalText string `validate:"required,gt=0,lte=65536" json:"original_text"`
// parsed text
ParsedText string `json:"-"`
// user id
UserID string `json:"-"`
}
func (req *AddTagReq) Check() (errFields []*validator.FormErrorField, err error) {
req.ParsedText = converter.Markdown2HTML(req.OriginalText)
req.SlugName = strings.ToLower(req.SlugName)
return nil, nil
}
// AddTagResp add tag response
type AddTagResp struct {
SlugName string `json:"slug_name"`
}
// UpdateTagReq update tag request
type UpdateTagReq struct {
// tag_id
+3 -7
View File
@@ -68,8 +68,8 @@ type GetUserResp struct {
Language string `json:"language"`
// access token
AccessToken string `json:"access_token"`
// is admin
IsAdmin bool `json:"is_admin"`
// role id
RoleID int `json:"role_id"`
// user status
Status string `json:"status"`
}
@@ -167,11 +167,7 @@ type GetOtherUserInfoByUsernameResp struct {
// website
Website string `json:"website"`
// location
Location string `json:"location"`
// ip info
IPInfo string `json:"ip_info"`
// is admin
IsAdmin bool `json:"is_admin"`
Location string `json:"location"`
Status string `json:"status"`
StatusMsg string `json:"status_msg,omitempty"`
}
+9 -1
View File
@@ -20,6 +20,7 @@ import (
"github.com/answerdev/answer/internal/service/permission"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/answerdev/answer/internal/service/revision_common"
"github.com/answerdev/answer/internal/service/role"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/encryption"
"github.com/segmentfault/pacman/errors"
@@ -39,6 +40,7 @@ type AnswerService struct {
AnswerCommon *answercommon.AnswerCommon
voteRepo activity_common.VoteRepo
emailService *export.EmailService
roleService *role.UserRoleRelService
}
func NewAnswerService(
@@ -53,6 +55,7 @@ func NewAnswerService(
answerCommon *answercommon.AnswerCommon,
voteRepo activity_common.VoteRepo,
emailService *export.EmailService,
roleService *role.UserRoleRelService,
) *AnswerService {
return &AnswerService{
answerRepo: answerRepo,
@@ -66,6 +69,7 @@ func NewAnswerService(
AnswerCommon: answerCommon,
voteRepo: voteRepo,
emailService: emailService,
roleService: roleService,
}
}
@@ -82,7 +86,11 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
if answerInfo.Status == entity.AnswerStatusDeleted {
return nil
}
if !req.IsAdmin {
roleID, err := as.roleService.GetUserRole(ctx, req.UserID)
if err != nil {
return err
}
if roleID != role.RoleAdminID && roleID != role.RoleModeratorID {
if answerInfo.UserID != req.UserID {
return errors.BadRequest(reason.AnswerCannotDeleted)
}
+1 -1
View File
@@ -45,7 +45,7 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
log.Debugf("user status updated: %+v", cacheInfo)
userCacheInfo.UserStatus = cacheInfo.UserStatus
userCacheInfo.EmailStatus = cacheInfo.EmailStatus
userCacheInfo.IsAdmin = cacheInfo.IsAdmin
userCacheInfo.RoleID = cacheInfo.RoleID
// update current user cache info
err := as.authRepo.SetUserCacheInfo(ctx, accessToken, userCacheInfo)
if err != nil {
+36
View File
@@ -222,6 +222,42 @@ func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (
return ts.TagFormat(ctx, tagsInfoList)
}
// AddTag get object tag
func (ts *TagCommonService) AddTag(ctx context.Context, req *schema.AddTagReq) (resp *schema.AddTagResp, err error) {
_, exist, err := ts.GetTagBySlugName(ctx, req.SlugName)
if err != nil {
return nil, err
}
if exist {
return nil, errors.BadRequest(reason.TagAlreadyExist)
}
tagInfo := &entity.Tag{
SlugName: req.SlugName,
DisplayName: req.DisplayName,
OriginalText: req.OriginalText,
ParsedText: req.ParsedText,
Status: entity.TagStatusAvailable,
UserID: req.UserID,
}
tagList := []*entity.Tag{tagInfo}
err = ts.tagCommonRepo.AddTagList(ctx, tagList)
if err != nil {
return nil, err
}
revisionDTO := &schema.AddRevisionDTO{
UserID: req.UserID,
ObjectID: tagInfo.ID,
Title: tagInfo.SlugName,
}
tagInfoJson, _ := json.Marshal(tagInfo)
revisionDTO.Content = string(tagInfoJson)
_, err = ts.revisionService.AddRevision(ctx, revisionDTO, true)
if err != nil {
return nil, err
}
return &schema.AddTagResp{SlugName: tagInfo.SlugName}, nil
}
// AddTagList get object tag
func (ts *TagCommonService) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) {
return ts.tagCommonRepo.AddTagList(ctx, tagList)
+10 -10
View File
@@ -81,7 +81,7 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
resp = &schema.GetUserToSetShowResp{}
resp.GetFromUserEntity(userInfo)
resp.AccessToken = token
resp.IsAdmin = roleID == role.RoleAdminID
resp.RoleID = roleID
return resp, nil
}
@@ -131,14 +131,14 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
UserID: userInfo.ID,
EmailStatus: userInfo.MailStatus,
UserStatus: userInfo.Status,
IsAdmin: roleID == role.RoleAdminID,
RoleID: roleID,
}
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
if err != nil {
return nil, err
}
resp.IsAdmin = userCacheInfo.IsAdmin
if resp.IsAdmin {
resp.RoleID = userCacheInfo.RoleID
if resp.RoleID == role.RoleAdminID {
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, userCacheInfo)
if err != nil {
return nil, err
@@ -365,14 +365,14 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
UserID: userInfo.ID,
EmailStatus: userInfo.MailStatus,
UserStatus: userInfo.Status,
IsAdmin: roleID == role.RoleAdminID,
RoleID: roleID,
}
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
if err != nil {
return nil, nil, err
}
resp.IsAdmin = userCacheInfo.IsAdmin
if resp.IsAdmin {
resp.RoleID = userCacheInfo.RoleID
if resp.RoleID == role.RoleAdminID {
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
if err != nil {
return nil, nil, err
@@ -457,7 +457,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
UserID: userInfo.ID,
EmailStatus: userInfo.MailStatus,
UserStatus: userInfo.Status,
IsAdmin: roleID == role.RoleAdminID,
RoleID: roleID,
}
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
if err != nil {
@@ -467,8 +467,8 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
if err = us.authService.SetUserStatus(ctx, userCacheInfo); err != nil {
return nil, err
}
resp.IsAdmin = userCacheInfo.IsAdmin
if resp.IsAdmin {
resp.RoleID = userCacheInfo.RoleID
if resp.RoleID == role.RoleAdminID {
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
if err != nil {
return nil, err