This is an automated email from the ASF dual-hosted git repository.
LinkinStars pushed a commit to branch fix/2.0.1/chat
in repository https://gitbox.apache.org/repos/asf/answer.git
The following commit(s) were added to refs/heads/fix/2.0.1/chat by this push:
new 11c80384 fix(chat): enhance visibility checks by adding admin
moderator support in answer and comment services
11c80384 is described below
commit 11c80384f13a27e27c871c05bb353489d1363ee2
Author: LinkinStars <[email protected]>
AuthorDate: Sat May 9 15:13:50 2026 +0800
fix(chat): enhance visibility checks by adding admin moderator support in
answer and comment services
---
internal/controller/answer_controller.go | 8 +-
internal/controller/comment_controller.go | 2 +
internal/controller/question_controller.go | 3 +-
internal/schema/answer_schema.go | 19 ++---
internal/schema/comment_schema.go | 6 +-
internal/schema/question_schema.go | 1 +
internal/schema/simple_obj_info_schema.go | 113 +++++++++++++++++++++++----
internal/service/comment/comment_service.go | 14 ++++
internal/service/content/answer_service.go | 31 +++++++-
internal/service/content/question_service.go | 3 +
internal/service/content/revision_service.go | 13 +--
internal/service/object_info/object_info.go | 39 ++++-----
12 files changed, 193 insertions(+), 59 deletions(-)
diff --git a/internal/controller/answer_controller.go
b/internal/controller/answer_controller.go
index 99c89b77..6e16c6a8 100644
--- a/internal/controller/answer_controller.go
+++ b/internal/controller/answer_controller.go
@@ -164,8 +164,9 @@ func (ac *AnswerController) GetAnswerInfo(ctx *gin.Context)
{
id := ctx.Query("id")
id = uid.DeShortID(id)
userID := middleware.GetLoginUserIDFromContext(ctx)
+ isAdminModerator := middleware.GetUserIsAdminModerator(ctx)
- info, questionInfo, has, err := ac.answerService.Get(ctx, id, userID)
+ info, questionInfo, has, err := ac.answerService.Get(ctx, id, userID,
isAdminModerator)
if err != nil {
handler.HandleResponse(ctx, err, gin.H{})
return
@@ -271,7 +272,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) {
if !isAdmin || !linkUrlLimitUser {
ac.actionService.ActionRecordAdd(ctx,
entity.CaptchaActionAnswer, req.UserID)
}
- info, questionInfo, has, err := ac.answerService.Get(ctx, answerID,
req.UserID)
+ info, questionInfo, has, err := ac.answerService.Get(ctx, answerID,
req.UserID, isAdmin)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
@@ -348,7 +349,7 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
if !isAdmin || !linkUrlLimitUser {
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit,
req.UserID)
}
- _, _, _, err = ac.answerService.Get(ctx, req.ID, req.UserID)
+ _, _, _, err = ac.answerService.Get(ctx, req.ID, req.UserID, isAdmin)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
@@ -376,6 +377,7 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
req.QuestionID = uid.DeShortID(req.QuestionID)
+ req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
canList, err := ac.rankService.CheckOperationPermissions(ctx,
req.UserID, []string{
permission.AnswerEdit,
diff --git a/internal/controller/comment_controller.go
b/internal/controller/comment_controller.go
index 7289a0e1..b9beead9 100644
--- a/internal/controller/comment_controller.go
+++ b/internal/controller/comment_controller.go
@@ -248,6 +248,7 @@ func (cc *CommentController) GetCommentWithPage(ctx
*gin.Context) {
req.ObjectID = uid.DeShortID(req.ObjectID)
req.CommentID = uid.DeShortID(req.CommentID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
+ req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
canList, err := cc.rankService.CheckOperationPermissions(ctx,
req.UserID, []string{
permission.CommentEdit,
permission.CommentDelete,
@@ -300,6 +301,7 @@ func (cc *CommentController) GetComment(ctx *gin.Context) {
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
+ req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
canList, err := cc.rankService.CheckOperationPermissions(ctx,
req.UserID, []string{
permission.CommentEdit,
permission.CommentDelete,
diff --git a/internal/controller/question_controller.go
b/internal/controller/question_controller.go
index d5164fc8..05ad319a 100644
--- a/internal/controller/question_controller.go
+++ b/internal/controller/question_controller.go
@@ -234,6 +234,7 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context)
{
id = uid.DeShortID(id)
userID := middleware.GetLoginUserIDFromContext(ctx)
req := schema.QuestionPermission{}
+ req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
canList, err := qc.rankService.CheckOperationPermissions(ctx, userID,
[]string{
permission.QuestionEdit,
permission.QuestionDelete,
@@ -590,7 +591,7 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx
*gin.Context) {
handler.HandleResponse(ctx, err, nil)
return
}
- info, questionInfo, has, err := qc.answerService.Get(ctx,
answerID, req.UserID)
+ info, questionInfo, has, err := qc.answerService.Get(ctx,
answerID, req.UserID, isAdmin)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go
index 9ac4bcad..bf80c56e 100644
--- a/internal/schema/answer_schema.go
+++ b/internal/schema/answer_schema.go
@@ -106,15 +106,16 @@ type AnswerUpdateResp struct {
}
type AnswerListReq struct {
- QuestionID string `json:"question_id" form:"question_id"`
- Order string `json:"order" form:"order"`
- Page int `json:"page" form:"page"`
- PageSize int `json:"page_size" form:"page_size"`
- UserID string `json:"-"`
- IsAdmin bool `json:"-"`
- CanEdit bool `json:"-"`
- CanDelete bool `json:"-"`
- CanRecover bool `json:"-"`
+ QuestionID string `json:"question_id" form:"question_id"`
+ Order string `json:"order" form:"order"`
+ Page int `json:"page" form:"page"`
+ PageSize int `json:"page_size" form:"page_size"`
+ UserID string `json:"-"`
+ IsAdmin bool `json:"-"`
+ IsAdminModerator bool `json:"-"`
+ CanEdit bool `json:"-"`
+ CanDelete bool `json:"-"`
+ CanRecover bool `json:"-"`
}
type AnswerInfo struct {
diff --git a/internal/schema/comment_schema.go
b/internal/schema/comment_schema.go
index 015da071..a9c8a21a 100644
--- a/internal/schema/comment_schema.go
+++ b/internal/schema/comment_schema.go
@@ -150,7 +150,8 @@ type GetCommentWithPageReq struct {
// query condition
QueryCond string `validate:"omitempty,oneof=vote created_at"
form:"query_cond"`
// user id
- UserID string `json:"-"`
+ UserID string `json:"-"`
+ IsAdminModerator bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
@@ -162,7 +163,8 @@ type GetCommentReq struct {
// object id
ID string `validate:"required" form:"id"`
// user id
- UserID string `json:"-"`
+ UserID string `json:"-"`
+ IsAdminModerator bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
diff --git a/internal/schema/question_schema.go
b/internal/schema/question_schema.go
index 13320828..4c231385 100644
--- a/internal/schema/question_schema.go
+++ b/internal/schema/question_schema.go
@@ -143,6 +143,7 @@ func (req *QuestionAddByAnswer) Check() (errFields
[]*validator.FormErrorField,
}
type QuestionPermission struct {
+ IsAdminModerator bool `json:"-"`
// whether user can add it
CanAdd bool `json:"-"`
// whether user can edit it
diff --git a/internal/schema/simple_obj_info_schema.go
b/internal/schema/simple_obj_info_schema.go
index 89763e86..eebdd898 100644
--- a/internal/schema/simple_obj_info_schema.go
+++ b/internal/schema/simple_obj_info_schema.go
@@ -21,25 +21,28 @@ package schema
import (
"github.com/apache/answer/internal/base/constant"
+ "github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/entity"
+ "github.com/segmentfault/pacman/errors"
)
// SimpleObjectInfo simple object info
type SimpleObjectInfo struct {
- ObjectID string `json:"object_id"`
- ObjectCreatorUserID string `json:"object_creator_user_id"`
- QuestionID string `json:"question_id"`
- QuestionStatus int `json:"question_status"`
- QuestionShow int `json:"question_show"`
- AnswerID string `json:"answer_id"`
- AnswerStatus int `json:"answer_status"`
- CommentID string `json:"comment_id"`
- CommentStatus int `json:"comment_status"`
- TagID string `json:"tag_id"`
- TagStatus int `json:"tag_status"`
- ObjectType string `json:"object_type"`
- Title string `json:"title"`
- Content string `json:"content"`
+ ObjectID string `json:"object_id"`
+ ObjectCreatorUserID string `json:"object_creator_user_id"`
+ QuestionID string `json:"question_id"`
+ QuestionCreatorUserID string `json:"question_creator_user_id"`
+ QuestionStatus int `json:"question_status"`
+ QuestionShow int `json:"question_show"`
+ AnswerID string `json:"answer_id"`
+ AnswerStatus int `json:"answer_status"`
+ CommentID string `json:"comment_id"`
+ CommentStatus int `json:"comment_status"`
+ TagID string `json:"tag_id"`
+ TagStatus int `json:"tag_status"`
+ ObjectType string `json:"object_type"`
+ Title string `json:"title"`
+ Content string `json:"content"`
}
// IsDeleted is deleted
@@ -57,6 +60,88 @@ func (s *SimpleObjectInfo) IsDeleted() bool {
return false
}
+func (s *SimpleObjectInfo) CheckVisibility(userID string, isAdminModerator
bool) error {
+ if s == nil {
+ return errors.NotFound(reason.ObjectNotFound)
+ }
+ if s.isObjectRestricted() && !s.canViewObject(userID, isAdminModerator)
{
+ return errors.NotFound(s.objectNotFoundReason())
+ }
+ if s.hasParentQuestion() && s.isParentQuestionRestricted() &&
+ !s.canViewParentQuestion(userID, isAdminModerator) {
+ return errors.NotFound(reason.QuestionNotFound)
+ }
+ return nil
+}
+
+func (s *SimpleObjectInfo) canViewObject(userID string, isAdminModerator bool)
bool {
+ if isAdminModerator {
+ return true
+ }
+ switch s.ObjectType {
+ case constant.QuestionObjectType:
+ return s.QuestionCreatorUserID == userID
+ case constant.AnswerObjectType, constant.CommentObjectType,
constant.TagObjectType:
+ return s.ObjectCreatorUserID == userID
+ default:
+ return false
+ }
+}
+
+func (s *SimpleObjectInfo) canViewParentQuestion(userID string,
isAdminModerator bool) bool {
+ if isAdminModerator {
+ return true
+ }
+ return s.QuestionCreatorUserID == userID
+}
+
+func (s *SimpleObjectInfo) hasParentQuestion() bool {
+ switch s.ObjectType {
+ case constant.AnswerObjectType, constant.CommentObjectType:
+ return len(s.QuestionID) > 0 && s.QuestionID != "0"
+ default:
+ return false
+ }
+}
+
+func (s *SimpleObjectInfo) isObjectRestricted() bool {
+ switch s.ObjectType {
+ case constant.QuestionObjectType:
+ return s.QuestionStatus == entity.QuestionStatusDeleted ||
+ s.QuestionStatus == entity.QuestionStatusPending ||
+ s.QuestionShow == entity.QuestionHide
+ case constant.AnswerObjectType:
+ return s.AnswerStatus == entity.AnswerStatusDeleted ||
s.AnswerStatus == entity.AnswerStatusPending
+ case constant.CommentObjectType:
+ return s.CommentStatus == entity.CommentStatusDeleted ||
s.CommentStatus == entity.CommentStatusPending
+ case constant.TagObjectType:
+ return s.TagStatus == entity.TagStatusDeleted
+ default:
+ return false
+ }
+}
+
+func (s *SimpleObjectInfo) isParentQuestionRestricted() bool {
+ return s.QuestionStatus == entity.QuestionStatusDeleted ||
+ s.QuestionStatus == entity.QuestionStatusPending ||
+ s.QuestionShow == entity.QuestionHide
+}
+
+func (s *SimpleObjectInfo) objectNotFoundReason() string {
+ switch s.ObjectType {
+ case constant.QuestionObjectType:
+ return reason.QuestionNotFound
+ case constant.AnswerObjectType:
+ return reason.AnswerNotFound
+ case constant.CommentObjectType:
+ return reason.CommentNotFound
+ case constant.TagObjectType:
+ return reason.TagNotFound
+ default:
+ return reason.ObjectNotFound
+ }
+}
+
type UnreviewedRevisionInfoInfo struct {
CreatedAt int64 `json:"created_at"`
ObjectID string `json:"object_id"`
diff --git a/internal/service/comment/comment_service.go
b/internal/service/comment/comment_service.go
index 30ff43c6..60915ff3 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -316,6 +316,13 @@ func (cs *CommentService) GetComment(ctx context.Context,
req *schema.GetComment
if !exist {
return nil, errors.BadRequest(reason.CommentNotFound)
}
+ objInfo, err := cs.objectInfoService.GetInfo(ctx, comment.ObjectID)
+ if err != nil {
+ return nil, err
+ }
+ if err := objInfo.CheckVisibility(req.UserID, req.IsAdminModerator);
err != nil {
+ return nil, err
+ }
resp = &schema.GetCommentResp{
CommentID: comment.ID,
@@ -367,6 +374,13 @@ func (cs *CommentService) GetComment(ctx context.Context,
req *schema.GetComment
// GetCommentWithPage get comment list page
func (cs *CommentService) GetCommentWithPage(ctx context.Context, req
*schema.GetCommentWithPageReq) (
pageModel *pager.PageModel, err error) {
+ objInfo, err := cs.objectInfoService.GetInfo(ctx, req.ObjectID)
+ if err != nil {
+ return nil, err
+ }
+ if err := objInfo.CheckVisibility(req.UserID, req.IsAdminModerator);
err != nil {
+ return nil, err
+ }
dto := &CommentQuery{
PageCond: pager.PageCond{Page: req.Page, PageSize:
req.PageSize},
ObjectID: req.ObjectID,
diff --git a/internal/service/content/answer_service.go
b/internal/service/content/answer_service.go
index 2ad87517..50975d2d 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -512,11 +512,27 @@ func (as *AnswerService) updateAnswerRank(ctx
context.Context, userID string,
}
}
-func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID
string) (*schema.AnswerInfo, *schema.QuestionInfoResp, bool, error) {
+func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID
string, isAdminModerator bool) (*schema.AnswerInfo, *schema.QuestionInfoResp,
bool, error) {
answerInfo, has, err := as.answerRepo.GetByID(ctx, answerID)
if err != nil {
return nil, nil, has, err
}
+ if !has {
+ return nil, nil, false, nil
+ }
+ question, exist, err := as.questionRepo.GetQuestion(ctx,
answerInfo.QuestionID)
+ if err != nil {
+ return nil, nil, has, err
+ }
+ if !exist {
+ return nil, nil, false, errors.NotFound(reason.AnswerNotFound)
+ }
+ if (question.Status == entity.QuestionStatusDeleted ||
+ question.Status == entity.QuestionStatusPending ||
+ question.Show == entity.QuestionHide) &&
+ !isAdminModerator && question.UserID != loginUserID {
+ return nil, nil, false, errors.NotFound(reason.AnswerNotFound)
+ }
info := as.ShowFormat(ctx, answerInfo)
// todo questionFunc
questionInfo, err := as.questionCommon.Info(ctx, answerInfo.QuestionID,
loginUserID)
@@ -621,6 +637,19 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx
context.Context, req *schema.A
func (as *AnswerService) SearchList(ctx context.Context, req
*schema.AnswerListReq) ([]*schema.AnswerInfo, int64, error) {
list := make([]*schema.AnswerInfo, 0)
+ questionInfo, exist, err := as.questionRepo.GetQuestion(ctx,
req.QuestionID)
+ if err != nil {
+ return list, 0, err
+ }
+ if !exist {
+ return list, 0, errors.NotFound(reason.QuestionNotFound)
+ }
+ if (questionInfo.Status == entity.QuestionStatusDeleted ||
+ questionInfo.Status == entity.QuestionStatusPending ||
+ questionInfo.Show == entity.QuestionHide) &&
+ !req.IsAdminModerator && questionInfo.UserID != req.UserID {
+ return list, 0, errors.NotFound(reason.QuestionNotFound)
+ }
dbSearch := entity.AnswerSearch{}
dbSearch.QuestionID = req.QuestionID
dbSearch.Page = req.Page
diff --git a/internal/service/content/question_service.go
b/internal/service/content/question_service.go
index bc3ac0bb..8c8da1fd 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -1086,6 +1086,9 @@ func (qs *QuestionService) GetQuestion(ctx
context.Context, questionID, userID s
question.Status == entity.QuestionStatusPending) &&
!per.CanReopen && question.UserID != userID {
return nil, errors.NotFound(reason.QuestionNotFound)
}
+ if question.Show == entity.QuestionHide && !per.IsAdminModerator &&
question.UserID != userID {
+ return nil, errors.NotFound(reason.QuestionNotFound)
+ }
if question.Status != entity.QuestionStatusClosed {
per.CanReopen = false
}
diff --git a/internal/service/content/revision_service.go
b/internal/service/content/revision_service.go
index 66e6181f..83568088 100644
--- a/internal/service/content/revision_service.go
+++ b/internal/service/content/revision_service.go
@@ -392,17 +392,8 @@ func (rs *RevisionService) GetRevisionList(ctx
context.Context, req *schema.GetR
if infoErr != nil {
return nil, infoErr
}
- if !req.IsAdmin && objInfo.IsDeleted() && objInfo.ObjectCreatorUserID
!= req.UserID {
- switch objInfo.ObjectType {
- case constant.QuestionObjectType:
- return nil, errors.NotFound(reason.QuestionNotFound)
- case constant.AnswerObjectType:
- return nil, errors.NotFound(reason.AnswerNotFound)
- case constant.TagObjectType:
- return nil, errors.NotFound(reason.TagNotFound)
- default:
- return nil, errors.NotFound(reason.ObjectNotFound)
- }
+ if err := objInfo.CheckVisibility(req.UserID, req.IsAdmin); err != nil {
+ return nil, err
}
_ = copier.Copy(&rev, req)
diff --git a/internal/service/object_info/object_info.go
b/internal/service/object_info/object_info.go
index 4f91fbcb..0f36908d 100644
--- a/internal/service/object_info/object_info.go
+++ b/internal/service/object_info/object_info.go
@@ -200,14 +200,15 @@ func (os *ObjService) GetInfo(ctx context.Context,
objectID string) (objInfo *sc
break
}
objInfo = &schema.SimpleObjectInfo{
- ObjectID: questionInfo.ID,
- ObjectCreatorUserID: questionInfo.UserID,
- QuestionID: questionInfo.ID,
- QuestionStatus: questionInfo.Status,
- QuestionShow: questionInfo.Show,
- ObjectType: objectType,
- Title: questionInfo.Title,
- Content: questionInfo.ParsedText, // todo
trim
+ ObjectID: questionInfo.ID,
+ ObjectCreatorUserID: questionInfo.UserID,
+ QuestionID: questionInfo.ID,
+ QuestionCreatorUserID: questionInfo.UserID,
+ QuestionStatus: questionInfo.Status,
+ QuestionShow: questionInfo.Show,
+ ObjectType: objectType,
+ Title: questionInfo.Title,
+ Content: questionInfo.ParsedText, // todo
trim
}
case constant.AnswerObjectType:
answerInfo, exist, err := os.answerRepo.GetAnswer(ctx, objectID)
@@ -225,16 +226,17 @@ func (os *ObjService) GetInfo(ctx context.Context,
objectID string) (objInfo *sc
break
}
objInfo = &schema.SimpleObjectInfo{
- ObjectID: answerInfo.ID,
- ObjectCreatorUserID: answerInfo.UserID,
- QuestionID: answerInfo.QuestionID,
- QuestionStatus: questionInfo.Status,
- QuestionShow: questionInfo.Show,
- AnswerStatus: answerInfo.Status,
- AnswerID: answerInfo.ID,
- ObjectType: objectType,
- Title: questionInfo.Title, // this
should be question title
- Content: answerInfo.ParsedText, // todo trim
+ ObjectID: answerInfo.ID,
+ ObjectCreatorUserID: answerInfo.UserID,
+ QuestionID: answerInfo.QuestionID,
+ QuestionCreatorUserID: questionInfo.UserID,
+ QuestionStatus: questionInfo.Status,
+ QuestionShow: questionInfo.Show,
+ AnswerStatus: answerInfo.Status,
+ AnswerID: answerInfo.ID,
+ ObjectType: objectType,
+ Title: questionInfo.Title, // this
should be question title
+ Content: answerInfo.ParsedText, // todo
trim
}
case constant.CommentObjectType:
commentInfo, exist, err := os.commentRepo.GetComment(ctx,
objectID)
@@ -259,6 +261,7 @@ func (os *ObjService) GetInfo(ctx context.Context, objectID
string) (objInfo *sc
}
if exist {
objInfo.QuestionID = questionInfo.ID
+ objInfo.QuestionCreatorUserID =
questionInfo.UserID
objInfo.QuestionStatus = questionInfo.Status
objInfo.QuestionShow = questionInfo.Show
objInfo.Title = questionInfo.Title