fix: 管理删评对公众剥离作者身份,评论者本人仍可见原文
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -38,7 +38,7 @@ type BountyMeta struct {
|
||||
Escrowed bool `json:"escrowed"`
|
||||
Refunded bool `json:"refunded"`
|
||||
Expired bool `json:"expired"`
|
||||
EndsAt string `json:"ends_at"` // RFC3339;空表示待发布时补写
|
||||
EndsAt string `json:"ends_at"` // RFC3339;空表示待发布时补写
|
||||
ExpireDays int `json:"expire_days,omitempty"` // 发帖入参,规范化后可清
|
||||
}
|
||||
|
||||
@@ -58,16 +58,16 @@ type LotteryMeta struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPollClosed = errors.New("投票已结束")
|
||||
ErrAlreadyVoted = errors.New("你已投过票")
|
||||
ErrInvalidPollOpt = errors.New("无效的投票选项")
|
||||
ErrPollOptionsLocked = errors.New("已有人投票,无法修改选项或匿名设置")
|
||||
ErrBountySettled = errors.New("悬赏已结算")
|
||||
ErrBountyNotEscrow = errors.New("悬赏未托管")
|
||||
ErrLotteryDrawn = errors.New("已开奖")
|
||||
ErrLotteryNoEntries = errors.New("暂无回帖用户可抽奖")
|
||||
ErrPollClosed = errors.New("投票已结束")
|
||||
ErrAlreadyVoted = errors.New("你已投过票")
|
||||
ErrInvalidPollOpt = errors.New("无效的投票选项")
|
||||
ErrPollOptionsLocked = errors.New("已有人投票,无法修改选项或匿名设置")
|
||||
ErrBountySettled = errors.New("悬赏已结算")
|
||||
ErrBountyNotEscrow = errors.New("悬赏未托管")
|
||||
ErrLotteryDrawn = errors.New("已开奖")
|
||||
ErrLotteryNoEntries = errors.New("暂无回帖用户可抽奖")
|
||||
ErrLotteryPrizesLocked = errors.New("已有评论,无法修改奖品设置")
|
||||
ErrQuestionNotType = errors.New("本文不是问答帖")
|
||||
ErrQuestionNotType = errors.New("本文不是问答帖")
|
||||
)
|
||||
|
||||
func parseQuestionMeta(raw string) (*QuestionMeta, error) {
|
||||
@@ -476,17 +476,12 @@ func EnsureDeadlineOnPublish(typeMeta, postType string, publishedAt time.Time) (
|
||||
|
||||
// AcceptedAnswer 问答/悬赏已采纳答案(详情页正文下展示;与评论分页无关)
|
||||
type AcceptedAnswer struct {
|
||||
ID uint `json:"id"`
|
||||
Floor int `json:"floor"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
User struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
} `json:"user"`
|
||||
ID uint `json:"id"`
|
||||
Floor int `json:"floor"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
User *CommentActorBrief `json:"user,omitempty"` // 管理删对公众省略
|
||||
}
|
||||
|
||||
type QuestionState struct {
|
||||
@@ -554,17 +549,17 @@ type LotteryEntrant struct {
|
||||
}
|
||||
|
||||
type LotteryState struct {
|
||||
Slots int `json:"slots"`
|
||||
Prizes []LotteryPrize `json:"prizes"`
|
||||
Drawn bool `json:"drawn"`
|
||||
Closed bool `json:"closed"`
|
||||
EndsAt string `json:"ends_at,omitempty"`
|
||||
EntryCount int `json:"entry_count"`
|
||||
Eligible bool `json:"eligible"`
|
||||
CooldownHours int `json:"cooldown_hours"`
|
||||
CanDraw bool `json:"can_draw"`
|
||||
CanEditPrizes bool `json:"can_edit_prizes"`
|
||||
Winners []LotteryEntrant `json:"winners"`
|
||||
Slots int `json:"slots"`
|
||||
Prizes []LotteryPrize `json:"prizes"`
|
||||
Drawn bool `json:"drawn"`
|
||||
Closed bool `json:"closed"`
|
||||
EndsAt string `json:"ends_at,omitempty"`
|
||||
EntryCount int `json:"entry_count"`
|
||||
Eligible bool `json:"eligible"`
|
||||
CooldownHours int `json:"cooldown_hours"`
|
||||
CanDraw bool `json:"can_draw"`
|
||||
CanEditPrizes bool `json:"can_edit_prizes"`
|
||||
Winners []LotteryEntrant `json:"winners"`
|
||||
}
|
||||
|
||||
func (s *PostService) fillInteractState(detail *PostDetail, post *model.Post, viewerID uint, loadActor func() *Actor) {
|
||||
@@ -580,23 +575,23 @@ func (s *PostService) fillInteractState(detail *PostDetail, post *model.Post, vi
|
||||
|
||||
switch pt {
|
||||
case model.PostTypeQuestion:
|
||||
detail.Question = s.buildQuestionState(post, canManage)
|
||||
detail.Question = s.buildQuestionState(post, viewerID, canManage, isMod)
|
||||
case model.PostTypePoll:
|
||||
detail.Poll = s.buildPollState(post, viewerID, canManage)
|
||||
case model.PostTypeBounty:
|
||||
detail.Bounty = s.buildBountyState(post, canManage)
|
||||
detail.Bounty = s.buildBountyState(post, viewerID, canManage, isMod)
|
||||
case model.PostTypeLottery:
|
||||
detail.Lottery = s.buildLotteryState(post, viewerID, canManage)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PostService) buildQuestionState(post *model.Post, canManage bool) *QuestionState {
|
||||
func (s *PostService) buildQuestionState(post *model.Post, viewerID uint, canManage, isMod bool) *QuestionState {
|
||||
m, err := parseQuestionMeta(post.TypeMeta)
|
||||
if err != nil || m == nil {
|
||||
m = &QuestionMeta{}
|
||||
}
|
||||
floor := FloorNumber(s.db, post.ID, m.AcceptedCommentID)
|
||||
answer := s.loadAcceptedAnswer(post.ID, m.AcceptedCommentID, floor)
|
||||
answer := s.loadAcceptedAnswer(post.ID, m.AcceptedCommentID, floor, viewerID, isMod)
|
||||
solvedAt := m.SolvedAt
|
||||
if m.Solved && solvedAt == "" && answer != nil && !answer.CreatedAt.IsZero() {
|
||||
solvedAt = answer.CreatedAt.UTC().Format(time.RFC3339) // 旧数据:无 solved_at 时用采纳评论时间
|
||||
@@ -610,7 +605,7 @@ func (s *PostService) buildQuestionState(post *model.Post, canManage bool) *Ques
|
||||
CanAccept: canManage && !m.Solved,
|
||||
CanSolve: canManage && !m.Solved,
|
||||
// 已采纳答案后不可重新打开;仅「手动标已解决」可撤回
|
||||
CanReopen: canManage && m.Solved && m.AcceptedCommentID == 0,
|
||||
CanReopen: canManage && m.Solved && m.AcceptedCommentID == 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,7 +708,7 @@ func (s *PostService) buildPollState(post *model.Post, viewerID uint, canClose b
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PostService) buildBountyState(post *model.Post, canManage bool) *BountyState {
|
||||
func (s *PostService) buildBountyState(post *model.Post, viewerID uint, canManage, isMod bool) *BountyState {
|
||||
m, err := parseBountyMeta(post.TypeMeta)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -724,7 +719,7 @@ func (s *PostService) buildBountyState(post *model.Post, canManage bool) *Bounty
|
||||
Points: m.Points,
|
||||
AcceptedCommentID: m.AcceptedCommentID,
|
||||
AcceptedFloor: floor,
|
||||
AcceptedAnswer: s.loadAcceptedAnswer(post.ID, m.AcceptedCommentID, floor),
|
||||
AcceptedAnswer: s.loadAcceptedAnswer(post.ID, m.AcceptedCommentID, floor, viewerID, isMod),
|
||||
Escrowed: m.Escrowed,
|
||||
Refunded: m.Refunded,
|
||||
Expired: m.Expired,
|
||||
@@ -736,7 +731,8 @@ func (s *PostService) buildBountyState(post *model.Post, canManage bool) *Bounty
|
||||
}
|
||||
|
||||
// loadAcceptedAnswer 加载已采纳评论快照(公开可见正文);软删则清空正文并标记 deleted。
|
||||
func (s *PostService) loadAcceptedAnswer(postID, commentID uint, floor int) *AcceptedAnswer {
|
||||
// 管理删:公众不返回评论者身份(与评论树 toCommentNode 一致)。
|
||||
func (s *PostService) loadAcceptedAnswer(postID, commentID uint, floor int, viewerID uint, isMod bool) *AcceptedAnswer {
|
||||
if postID == 0 || commentID == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -756,13 +752,23 @@ func (s *PostService) loadAcceptedAnswer(postID, commentID uint, floor int) *Acc
|
||||
Floor: floor,
|
||||
CreatedAt: c.CreatedAt,
|
||||
}
|
||||
ans.User.ID = c.User.ID
|
||||
ans.User.Username = c.User.Username
|
||||
ans.User.Nickname = c.User.Nickname
|
||||
ans.User.Avatar = c.User.Avatar
|
||||
staff := c.DeletedAt.Valid && c.DeletedBy != 0 && c.DeletedBy != c.UserID
|
||||
hideAuthor := staff && !isMod && viewerID != c.UserID
|
||||
if !hideAuthor && c.User.ID != 0 {
|
||||
ans.User = &CommentActorBrief{
|
||||
ID: c.User.ID,
|
||||
Username: c.User.Username,
|
||||
Nickname: c.User.Nickname,
|
||||
Avatar: c.User.Avatar,
|
||||
}
|
||||
}
|
||||
if c.DeletedAt.Valid {
|
||||
ans.Deleted = true
|
||||
ans.Content = ""
|
||||
if isMod || viewerID == c.UserID {
|
||||
ans.Content = c.Content
|
||||
} else {
|
||||
ans.Content = ""
|
||||
}
|
||||
return ans
|
||||
}
|
||||
ans.Content = c.Content
|
||||
@@ -1513,12 +1519,10 @@ func (s *PostService) loadManageablePost(actor *Actor, userID, postID uint) (*mo
|
||||
|
||||
func (s *PostService) detailAfterInteract(post *model.Post, viewerID uint) (*PostDetail, error) {
|
||||
detail := buildPostDetail(post)
|
||||
locked, hint := s.evalContentAccess(post, viewerID, nil)
|
||||
detail.ContentLocked = locked
|
||||
sanitized, fullyLocked, hint := s.sanitizePostContent(post, viewerID, nil)
|
||||
detail.Content = sanitized
|
||||
detail.ContentLocked = fullyLocked
|
||||
detail.AccessHint = hint
|
||||
if locked {
|
||||
detail.Content = ""
|
||||
}
|
||||
atts, _ := s.listAttachmentDTOs(post.ID, viewerID, post.UserID)
|
||||
detail.Attachments = atts
|
||||
s.fillInteractState(detail, post, viewerID, nil)
|
||||
|
||||
Reference in New Issue
Block a user