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

Reply via email to