From 18161c30b731afb04fad118301c6d6efac0ced67 Mon Sep 17 00:00:00 2001 From: rafmaster7 Date: Wed, 16 Sep 2026 18:25:14 +0200 Subject: [PATCH] fix: run the reviewer on comment edits, tell the author when a comment is pending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comments were only reviewed on creation, so a harmless comment could be edited into anything within the edit window and skip the review queue. UpdateComment now sends the edited text through ReviewService. AddCommentReview like a new comment; a "needs review" verdict sets the comment to pending and creates the review entry (staff are skipped by the reviewer plugin as before). The add / update responses carry the comment status, so the UI hides a pending comment and shows messages.post_pending instead of leaving the author with a comment that disappears on the next reload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0181uZ2px83HmPo3HKSEzWQR Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0181uZ2px83HmPo3HKSEzWQR --- internal/controller/comment_controller.go | 2 ++ internal/schema/comment_schema.go | 7 ++++++ internal/service/comment/comment_service.go | 14 +++++++++++ ui/src/components/Comment/index.tsx | 27 ++++++++++++++++++++- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go index b9beead94..73a8f9980 100644 --- a/internal/controller/comment_controller.go +++ b/internal/controller/comment_controller.go @@ -195,6 +195,8 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) { req.UserID = middleware.GetLoginUserIDFromContext(ctx) req.IsAdmin = middleware.GetIsAdminFromContext(ctx) + req.IP = ctx.ClientIP() + req.UserAgent = ctx.GetHeader("User-Agent") canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{ permission.CommentEdit, permission.LinkUrlLimit, diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go index a9c8a21a3..9538649b6 100644 --- a/internal/schema/comment_schema.go +++ b/internal/schema/comment_schema.go @@ -88,6 +88,9 @@ type UpdateCommentReq struct { // user id UserID string `json:"-"` IsAdmin bool `json:"-"` + // for the reviewer plugin (edits go through review like new comments) + IP string `json:"-"` + UserAgent string `json:"-"` // whether user can edit it CanEdit bool `json:"-"` @@ -111,6 +114,8 @@ func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err type UpdateCommentResp struct { // comment id CommentID string `json:"comment_id"` + // status after the edit was reviewed (11 = pending, hidden until a moderator approves it) + Status int `json:"status"` // original comment content OriginalText string `json:"original_text"` // parsed comment content @@ -175,6 +180,8 @@ type GetCommentReq struct { type GetCommentResp struct { // comment id CommentID string `json:"comment_id"` + // comment status (11 = pending review) so the UI can tell the author + Status int `json:"status"` // create time CreatedAt int64 `json:"created_at"` diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go index 0decd5847..f6d4ecb99 100644 --- a/internal/service/comment/comment_service.go +++ b/internal/service/comment/comment_service.go @@ -323,10 +323,24 @@ func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateC if err = cs.commentRepo.UpdateCommentContent(ctx, old.ID, req.OriginalText, req.ParsedText); err != nil { return nil, err } + // an edit is reviewed like a new comment (upstream only reviews on creation, so a benign + // comment could be edited into anything). Staff are skipped by the reviewer plugin itself. + newStatus := old.Status + if old.Status == entity.CommentStatusAvailable { + edited := *old + edited.OriginalText, edited.ParsedText = req.OriginalText, req.ParsedText + if status := cs.reviewService.AddCommentReview(ctx, &edited, req.IP, req.UserAgent); status != entity.CommentStatusAvailable { + if err = cs.commentCommonRepo.UpdateCommentStatus(ctx, old.ID, status); err != nil { + return nil, err + } + newStatus = status + } + } resp = &schema.UpdateCommentResp{ CommentID: old.ID, OriginalText: req.OriginalText, ParsedText: req.ParsedText, + Status: newStatus, } cs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventCommentUpdate, req.UserID).TID(old.ID). CID(old.ID, old.UserID)) diff --git a/ui/src/components/Comment/index.tsx b/ui/src/components/Comment/index.tsx index af2ddbd4f..c9e83dbb8 100644 --- a/ui/src/components/Comment/index.tsx +++ b/ui/src/components/Comment/index.tsx @@ -27,7 +27,12 @@ import unionBy from 'lodash/unionBy'; import * as Types from '@/common/interface'; import { Modal } from '@/components'; -import { usePageUsers, useReportModal, useCaptchaModal } from '@/hooks'; +import { + usePageUsers, + useReportModal, + useCaptchaModal, + useToast, +} from '@/hooks'; import { matchedUsers, parseUserInfo, @@ -78,6 +83,7 @@ const Comment: FC = ({ objectId, mode, commentId, children }) => { const editCaptcha = useCaptchaPlugin('edit'); const dCaptcha = useCaptchaPlugin('delete'); const vCaptcha = useCaptchaPlugin('vote'); + const toast = useToast(); const { t } = useTranslation('translation', { keyPrefix: 'comment' }); @@ -157,6 +163,15 @@ const Comment: FC = ({ objectId, mode, commentId, children }) => { return updateComment(up) .then(async (res) => { await editCaptcha?.close(); + // the edit went to the review queue: hide it and tell the author + if (res.status === 11) { + toast.onShow({ + msg: t('post_pending', { keyPrefix: 'messages' }), + variant: 'warning', + }); + setComments(comments.filter((c) => c.comment_id !== item.comment_id)); + return; + } setComments( comments.map((comment) => { if (comment.comment_id === item.comment_id) { @@ -191,6 +206,16 @@ const Comment: FC = ({ objectId, mode, commentId, children }) => { return addComment(req) .then(async (res) => { await addCaptcha?.close(); + // a new comment sent to the review queue is not shown, the author gets a notice + if (res.status === 11) { + toast.onShow({ + msg: t('post_pending', { keyPrefix: 'messages' }), + variant: 'warning', + }); + updateCurrentReplyId(''); + setVisibleComment(false); + return; + } if (item.type === 'reply') { const index = comments.findIndex( (comment) => comment.comment_id === item.comment_id,