/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package review import ( "context" "github.com/apache/answer/internal/base/constant" "github.com/apache/answer/internal/base/pager" "github.com/apache/answer/internal/base/reason" "github.com/apache/answer/internal/entity" "github.com/apache/answer/internal/schema" answercommon "github.com/apache/answer/internal/service/answer_common" commentcommon "github.com/apache/answer/internal/service/comment_common" "github.com/apache/answer/internal/service/noticequeue" "github.com/apache/answer/internal/service/object_info" questioncommon "github.com/apache/answer/internal/service/question_common" "github.com/apache/answer/internal/service/role" "github.com/apache/answer/internal/service/siteinfo_common" tagcommon "github.com/apache/answer/internal/service/tag_common" usercommon "github.com/apache/answer/internal/service/user_common" "github.com/apache/answer/internal/service/vector_sync" "github.com/apache/answer/pkg/htmltext" "github.com/apache/answer/pkg/token" "github.com/apache/answer/pkg/uid" "github.com/apache/answer/plugin" "github.com/jinzhu/copier" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" ) // ReviewRepo review repository type ReviewRepo interface { AddReview(ctx context.Context, review *entity.Review) (err error) UpdateReviewStatus(ctx context.Context, reviewID int, reviewerUserID string, status int) (err error) GetReview(ctx context.Context, reviewID int) (review *entity.Review, exist bool, err error) GetReviewByObject(ctx context.Context, objectID string) (review *entity.Review, exist bool, err error) GetReviewCount(ctx context.Context, status int) (count int64, err error) GetReviewPage(ctx context.Context, page, pageSize int, cond *entity.Review) (reviewList []*entity.Review, total int64, err error) } // ReviewService user service type ReviewService struct { reviewRepo ReviewRepo objectInfoService *object_info.ObjService userCommon *usercommon.UserCommon userRepo usercommon.UserRepo questionRepo questioncommon.QuestionRepo answerRepo answercommon.AnswerRepo userRoleService *role.UserRoleRelService tagCommon *tagcommon.TagCommonService questionCommon *questioncommon.QuestionCommon externalNotificationQueueService noticequeue.ExternalService notificationQueueService noticequeue.Service siteInfoService siteinfo_common.SiteInfoCommonService commentCommonRepo commentcommon.CommentCommonRepo vectorSyncService vector_sync.Service } // NewReviewService new review service func NewReviewService( reviewRepo ReviewRepo, objectInfoService *object_info.ObjService, userCommon *usercommon.UserCommon, userRepo usercommon.UserRepo, questionRepo questioncommon.QuestionRepo, answerRepo answercommon.AnswerRepo, userRoleService *role.UserRoleRelService, externalNotificationQueueService noticequeue.ExternalService, tagCommon *tagcommon.TagCommonService, questionCommon *questioncommon.QuestionCommon, notificationQueueService noticequeue.Service, siteInfoService siteinfo_common.SiteInfoCommonService, commentCommonRepo commentcommon.CommentCommonRepo, vectorSyncService vector_sync.Service, ) *ReviewService { return &ReviewService{ reviewRepo: reviewRepo, objectInfoService: objectInfoService, userCommon: userCommon, userRepo: userRepo, questionRepo: questionRepo, answerRepo: answerRepo, userRoleService: userRoleService, externalNotificationQueueService: externalNotificationQueueService, tagCommon: tagCommon, questionCommon: questionCommon, notificationQueueService: notificationQueueService, siteInfoService: siteInfoService, commentCommonRepo: commentCommonRepo, vectorSyncService: vectorSyncService, } } // AddQuestionReview add review for question if needed func (cs *ReviewService) AddQuestionReview(ctx context.Context, question *entity.Question, tags []*schema.TagItem, ip, ua string) (questionStatus int) { reviewContent := &plugin.ReviewContent{ ObjectType: constant.QuestionObjectType, Title: question.Title, Content: question.ParsedText, IP: ip, UserAgent: ua, } for _, tag := range tags { reviewContent.Tags = append(reviewContent.Tags, tag.SlugName) } reviewContent.Author = cs.getReviewContentAuthorInfo(ctx, question.UserID) reviewStatus := cs.callPluginToReview(ctx, question.UserID, question.ID, reviewContent) switch reviewStatus { case plugin.ReviewStatusApproved: questionStatus = entity.QuestionStatusAvailable case plugin.ReviewStatusNeedReview: questionStatus = entity.QuestionStatusPending case plugin.ReviewStatusDeleteDirectly: questionStatus = entity.QuestionStatusDeleted default: questionStatus = entity.QuestionStatusAvailable } return questionStatus } // AddAnswerReview add review for answer if needed func (cs *ReviewService) AddAnswerReview(ctx context.Context, answer *entity.Answer, ip, ua string) (answerStatus int) { reviewContent := &plugin.ReviewContent{ ObjectType: constant.AnswerObjectType, Content: answer.ParsedText, IP: ip, UserAgent: ua, } reviewContent.Author = cs.getReviewContentAuthorInfo(ctx, answer.UserID) reviewStatus := cs.callPluginToReview(ctx, answer.UserID, answer.ID, reviewContent) switch reviewStatus { case plugin.ReviewStatusApproved: answerStatus = entity.AnswerStatusAvailable case plugin.ReviewStatusNeedReview: answerStatus = entity.AnswerStatusPending case plugin.ReviewStatusDeleteDirectly: answerStatus = entity.AnswerStatusDeleted default: answerStatus = entity.AnswerStatusAvailable } return answerStatus } // AddCommentReview add review for comment if needed func (cs *ReviewService) AddCommentReview(ctx context.Context, comment *entity.Comment, ip, ua string) (commentStatus int) { reviewContent := &plugin.ReviewContent{ ObjectType: constant.CommentObjectType, Content: comment.ParsedText, IP: ip, UserAgent: ua, } reviewContent.Author = cs.getReviewContentAuthorInfo(ctx, comment.UserID) reviewStatus := cs.callPluginToReview(ctx, comment.UserID, comment.ID, reviewContent) switch reviewStatus { case plugin.ReviewStatusApproved: commentStatus = entity.CommentStatusAvailable case plugin.ReviewStatusNeedReview: commentStatus = entity.CommentStatusPending case plugin.ReviewStatusDeleteDirectly: commentStatus = entity.CommentStatusDeleted default: commentStatus = entity.CommentStatusAvailable } return commentStatus } // get review content author info func (cs *ReviewService) getReviewContentAuthorInfo(ctx context.Context, userID string) (author plugin.ReviewContentAuthor) { user, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, userID) if err != nil { log.Errorf("get user info failed, err: %v", err) return } if !exist { log.Errorf("user not found by id: %s", userID) return } author.Rank = user.Rank author.ApprovedQuestionAmount, _ = cs.questionRepo.GetUserQuestionCount(ctx, userID, 0) author.ApprovedAnswerAmount, _ = cs.answerRepo.GetCountByUserID(ctx, userID) author.Role, _ = cs.userRoleService.GetUserRole(ctx, userID) return } // call plugin to review func (cs *ReviewService) callPluginToReview(ctx context.Context, userID, objectID string, reviewContent *plugin.ReviewContent) (reviewStatus plugin.ReviewStatus) { // As default, no need review reviewStatus = plugin.ReviewStatusApproved objectID = uid.DeShortID(objectID) r := &entity.Review{ UserID: userID, ObjectID: objectID, ObjectType: constant.ObjectTypeStrMapping[reviewContent.ObjectType], ReviewerUserID: "0", Status: entity.ReviewStatusPending, } if siteInterface, _ := cs.siteInfoService.GetSiteInterface(ctx); siteInterface != nil { reviewContent.Language = siteInterface.Language } _ = plugin.CallReviewer(func(reviewer plugin.Reviewer) error { // If one of the reviewer plugin return false, then the review is not approved if reviewStatus != plugin.ReviewStatusApproved { return nil } if result := reviewer.Review(reviewContent); !result.Approved { reviewStatus = result.ReviewStatus r.Reason = result.Reason r.Submitter = reviewer.Info().SlugName } return nil }) if reviewStatus == plugin.ReviewStatusNeedReview { if err := cs.reviewRepo.AddReview(ctx, r); err != nil { log.Errorf("add review failed, err: %v", err) } } return reviewStatus } // UpdateReview update review func (cs *ReviewService) UpdateReview(ctx context.Context, req *schema.UpdateReviewReq) (err error) { review, exist, err := cs.reviewRepo.GetReview(ctx, req.ReviewID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if review.Status != entity.ReviewStatusPending { return nil } if err = cs.updateObjectStatus(ctx, review, req.IsApprove()); err != nil { return err } if req.IsApprove() { err = cs.reviewRepo.UpdateReviewStatus(ctx, req.ReviewID, req.UserID, entity.ReviewStatusApproved) } else { err = cs.reviewRepo.UpdateReviewStatus(ctx, req.ReviewID, req.UserID, entity.ReviewStatusRejected) } return } // update object status func (cs *ReviewService) updateObjectStatus(ctx context.Context, review *entity.Review, isApprove bool) (err error) { objectType := constant.ObjectTypeNumberMapping[review.ObjectType] switch objectType { case constant.QuestionObjectType: questionInfo, exist, err := cs.questionRepo.GetQuestion(ctx, review.ObjectID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if isApprove { questionInfo.Status = entity.QuestionStatusAvailable } else { questionInfo.Status = entity.QuestionStatusDeleted } if err := cs.questionRepo.UpdateQuestionStatus(ctx, questionInfo.ID, questionInfo.Status); err != nil { return err } if isApprove { tags, err := cs.tagCommon.GetObjectEntityTag(ctx, questionInfo.ID) if err != nil { log.Errorf("get question tags failed, err: %v", err) } cs.externalNotificationQueueService.Send(ctx, schema.CreateNewQuestionNotificationMsg(questionInfo.ID, questionInfo.Title, questionInfo.UserID, tags)) cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeQuestion, ObjectID: questionInfo.ID}) } else { cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionDelete, ObjectType: vector_sync.ObjectTypeQuestion, ObjectID: questionInfo.ID}) } userQuestionCount, err := cs.questionRepo.GetUserQuestionCount(ctx, questionInfo.UserID, 0) if err != nil { log.Errorf("get user question count failed, err: %v", err) } else { err = cs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, userQuestionCount) if err != nil { log.Errorf("update user question count failed, err: %v", err) } } case constant.AnswerObjectType: answerInfo, exist, err := cs.answerRepo.GetAnswer(ctx, review.ObjectID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if isApprove { answerInfo.Status = entity.AnswerStatusAvailable } else { answerInfo.Status = entity.AnswerStatusDeleted } if err := cs.answerRepo.UpdateAnswerStatus(ctx, answerInfo.ID, answerInfo.Status); err != nil { return err } questionInfo, exist, err := cs.questionRepo.GetQuestion(ctx, answerInfo.QuestionID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if isApprove { cs.notificationAnswerTheQuestion(ctx, questionInfo.UserID, questionInfo.ID, answerInfo.ID, answerInfo.UserID, questionInfo.Title, answerInfo.OriginalText) cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeAnswer, ObjectID: answerInfo.ID}) } else { cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionDelete, ObjectType: vector_sync.ObjectTypeAnswer, ObjectID: answerInfo.ID}) } cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeQuestion, ObjectID: answerInfo.QuestionID}) if err := cs.questionCommon.UpdateAnswerCount(ctx, answerInfo.QuestionID); err != nil { log.Errorf("update question answer count failed, err: %v", err) } if err := cs.questionCommon.UpdateLastAnswer(ctx, answerInfo.QuestionID, uid.DeShortID(answerInfo.ID)); err != nil { log.Errorf("update question last answer failed, err: %v", err) } userAnswerCount, err := cs.answerRepo.GetCountByUserID(ctx, answerInfo.UserID) if err != nil { log.Errorf("get user answer count failed, err: %v", err) } else { err = cs.userCommon.UpdateAnswerCount(ctx, answerInfo.UserID, int(userAnswerCount)) if err != nil { log.Errorf("update user answer count failed, err: %v", err) } } case constant.CommentObjectType: commentInfo, exist, err := cs.commentCommonRepo.GetCommentWithoutStatus(ctx, review.ObjectID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if isApprove { commentInfo.Status = entity.CommentStatusAvailable } else { commentInfo.Status = entity.CommentStatusDeleted } if err := cs.commentCommonRepo.UpdateCommentStatus(ctx, commentInfo.ID, commentInfo.Status); err != nil { return err } _, exist, err = cs.questionRepo.GetQuestion(ctx, commentInfo.QuestionID) if err != nil { return err } if !exist { return errors.BadRequest(reason.ObjectNotFound) } if isApprove { cs.notificationCommentOnTheQuestion(ctx, commentInfo) } if commentInfo.ObjectID == commentInfo.QuestionID { cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeQuestion, ObjectID: commentInfo.QuestionID}) } else { cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeAnswer, ObjectID: commentInfo.ObjectID}) cs.vectorSyncService.Send(ctx, &vector_sync.Task{Action: vector_sync.ActionUpsert, ObjectType: vector_sync.ObjectTypeQuestion, ObjectID: commentInfo.QuestionID}) } } return } func (cs *ReviewService) notificationAnswerTheQuestion(ctx context.Context, questionUserID, questionID, answerID, answerUserID, questionTitle, answerSummary string) { // If the question is answered by me, there is no notification for myself. if questionUserID == answerUserID { return } msg := &schema.NotificationMsg{ TriggerUserID: answerUserID, ReceiverUserID: questionUserID, Type: schema.NotificationTypeInbox, ObjectID: answerID, } msg.ObjectType = constant.AnswerObjectType msg.NotificationAction = constant.NotificationAnswerTheQuestion cs.notificationQueueService.Send(ctx, msg) receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID) if err != nil { log.Error(err) return } if !exist { log.Warnf("user %s not found", questionUserID) return } externalNotificationMsg := &schema.ExternalNotificationMsg{ ReceiverUserID: receiverUserInfo.ID, ReceiverEmail: receiverUserInfo.EMail, ReceiverLang: receiverUserInfo.Language, } rawData := &schema.NewAnswerTemplateRawData{ QuestionTitle: questionTitle, QuestionID: questionID, AnswerID: answerID, AnswerSummary: answerSummary, UnsubscribeCode: token.GenerateToken(), } answerUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, answerUserID) if answerUser != nil { rawData.AnswerUserDisplayName = answerUser.DisplayName } externalNotificationMsg.NewAnswerTemplateRawData = rawData cs.externalNotificationQueueService.Send(ctx, externalNotificationMsg) } func (cs *ReviewService) notificationCommentOnTheQuestion(ctx context.Context, comment *entity.Comment) { objInfo, err := cs.objectInfoService.GetInfo(ctx, comment.ObjectID) if err != nil { log.Error(err) return } if objInfo.IsDeleted() { log.Error("object already deleted") return } objInfo.ObjectID = uid.DeShortID(objInfo.ObjectID) objInfo.QuestionID = uid.DeShortID(objInfo.QuestionID) objInfo.AnswerID = uid.DeShortID(objInfo.AnswerID) // The priority of the notification // 1. reply to user // 2. comment mention to user // 3. answer or question was commented alreadyNotifiedUserID := make(map[string]bool) // get reply user info replyUserID := comment.GetReplyUserID() if len(replyUserID) > 0 && replyUserID != comment.UserID { replyUser, _, err := cs.userCommon.GetUserBasicInfoByID(ctx, replyUserID) if err != nil { log.Error(err) return } cs.notificationCommentReply(ctx, replyUser.ID, comment.ID, comment.UserID, objInfo.QuestionID, objInfo.Title, htmltext.FetchExcerpt(comment.ParsedText, "...", 240)) alreadyNotifiedUserID[replyUser.ID] = true return } mentionUsernameList := comment.GetMentionUsernameList() if len(mentionUsernameList) > 0 { alreadyNotifiedUserIDs := cs.notificationMention( ctx, mentionUsernameList, comment.ID, comment.UserID, alreadyNotifiedUserID) for _, userID := range alreadyNotifiedUserIDs { alreadyNotifiedUserID[userID] = true } return } if objInfo.ObjectType == constant.QuestionObjectType && !alreadyNotifiedUserID[objInfo.ObjectCreatorUserID] { cs.notificationQuestionComment(ctx, objInfo.ObjectCreatorUserID, objInfo.QuestionID, objInfo.Title, comment.ID, comment.UserID, htmltext.FetchExcerpt(comment.ParsedText, "...", 240)) } else if objInfo.ObjectType == constant.AnswerObjectType && !alreadyNotifiedUserID[objInfo.ObjectCreatorUserID] { cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID, objInfo.ObjectCreatorUserID, comment.ID, comment.UserID, htmltext.FetchExcerpt(comment.ParsedText, "...", 240)) } } func (cs *ReviewService) notificationCommentReply(ctx context.Context, replyUserID, commentID, commentUserID, questionID, questionTitle, commentSummary string) { msg := &schema.NotificationMsg{ ReceiverUserID: replyUserID, TriggerUserID: commentUserID, Type: schema.NotificationTypeInbox, ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType msg.NotificationAction = constant.NotificationReplyToYou cs.notificationQueueService.Send(ctx, msg) // Send external notification. receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, replyUserID) if err != nil { log.Error(err) return } if !exist { log.Warnf("user %s not found", replyUserID) return } externalNotificationMsg := &schema.ExternalNotificationMsg{ ReceiverUserID: receiverUserInfo.ID, ReceiverEmail: receiverUserInfo.EMail, ReceiverLang: receiverUserInfo.Language, } rawData := &schema.NewCommentTemplateRawData{ QuestionTitle: questionTitle, QuestionID: questionID, CommentID: commentID, CommentSummary: commentSummary, UnsubscribeCode: token.GenerateToken(), } commentUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, commentUserID) if commentUser != nil { rawData.CommentUserDisplayName = commentUser.DisplayName } externalNotificationMsg.NewCommentTemplateRawData = rawData cs.externalNotificationQueueService.Send(ctx, externalNotificationMsg) } func (cs *ReviewService) notificationMention( ctx context.Context, mentionUsernameList []string, commentID, commentUserID string, alreadyNotifiedUserID map[string]bool) (alreadyNotifiedUserIDs []string) { for _, username := range mentionUsernameList { userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, username) if err != nil { log.Error(err) continue } if exist && !alreadyNotifiedUserID[userInfo.ID] { msg := &schema.NotificationMsg{ ReceiverUserID: userInfo.ID, TriggerUserID: commentUserID, Type: schema.NotificationTypeInbox, ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType msg.NotificationAction = constant.NotificationMentionYou cs.notificationQueueService.Send(ctx, msg) alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID) } } return alreadyNotifiedUserIDs } func (cs *ReviewService) notificationQuestionComment(ctx context.Context, questionUserID, questionID, questionTitle, commentID, commentUserID, commentSummary string) { if questionUserID == commentUserID { return } // send internal notification msg := &schema.NotificationMsg{ ReceiverUserID: questionUserID, TriggerUserID: commentUserID, Type: schema.NotificationTypeInbox, ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType msg.NotificationAction = constant.NotificationCommentQuestion cs.notificationQueueService.Send(ctx, msg) // send external notification receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID) if err != nil { log.Error(err) return } if !exist { log.Warnf("user %s not found", questionUserID) return } externalNotificationMsg := &schema.ExternalNotificationMsg{ ReceiverUserID: receiverUserInfo.ID, ReceiverEmail: receiverUserInfo.EMail, ReceiverLang: receiverUserInfo.Language, } rawData := &schema.NewCommentTemplateRawData{ QuestionTitle: questionTitle, QuestionID: questionID, CommentID: commentID, CommentSummary: commentSummary, UnsubscribeCode: token.GenerateToken(), } commentUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, commentUserID) if commentUser != nil { rawData.CommentUserDisplayName = commentUser.DisplayName } externalNotificationMsg.NewCommentTemplateRawData = rawData cs.externalNotificationQueueService.Send(ctx, externalNotificationMsg) } func (cs *ReviewService) notificationAnswerComment(ctx context.Context, questionID, questionTitle, answerID, answerUserID, commentID, commentUserID, commentSummary string) { if answerUserID == commentUserID { return } // Send internal notification. msg := &schema.NotificationMsg{ ReceiverUserID: answerUserID, TriggerUserID: commentUserID, Type: schema.NotificationTypeInbox, ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType msg.NotificationAction = constant.NotificationCommentAnswer cs.notificationQueueService.Send(ctx, msg) // Send external notification. receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID) if err != nil { log.Error(err) return } if !exist { log.Warnf("user %s not found", answerUserID) return } externalNotificationMsg := &schema.ExternalNotificationMsg{ ReceiverUserID: receiverUserInfo.ID, ReceiverEmail: receiverUserInfo.EMail, ReceiverLang: receiverUserInfo.Language, } rawData := &schema.NewCommentTemplateRawData{ QuestionTitle: questionTitle, QuestionID: questionID, AnswerID: answerID, CommentID: commentID, CommentSummary: commentSummary, UnsubscribeCode: token.GenerateToken(), } commentUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, commentUserID) if commentUser != nil { rawData.CommentUserDisplayName = commentUser.DisplayName } externalNotificationMsg.NewCommentTemplateRawData = rawData cs.externalNotificationQueueService.Send(ctx, externalNotificationMsg) } // GetReviewPendingCount get review pending count func (cs *ReviewService) GetReviewPendingCount(ctx context.Context) (count int64, err error) { return cs.reviewRepo.GetReviewCount(ctx, entity.ReviewStatusPending) } // GetUnreviewedPostPage get review page func (cs *ReviewService) GetUnreviewedPostPage(ctx context.Context, req *schema.GetUnreviewedPostPageReq) ( pageModel *pager.PageModel, err error) { if !req.IsAdmin { return pager.NewPageModel(0, make([]*schema.GetUnreviewedPostPageResp, 0)), nil } cond := &entity.Review{ ObjectID: req.ObjectID, Status: entity.ReviewStatusPending, } reviewList, total, err := cs.reviewRepo.GetReviewPage(ctx, req.Page, 1, cond) if err != nil { return } resp := make([]*schema.GetUnreviewedPostPageResp, 0) for _, review := range reviewList { info, err := cs.objectInfoService.GetUnreviewedRevisionInfo(ctx, review.ObjectID) if err != nil { log.Errorf("GetUnreviewedRevisionInfo failed, err: %v", err) continue } r := &schema.GetUnreviewedPostPageResp{ ReviewID: review.ID, CreatedAt: info.CreatedAt, ObjectID: info.ObjectID, QuestionID: info.QuestionID, AnswerID: info.AnswerID, CommentID: info.CommentID, ObjectType: info.ObjectType, Title: info.Title, UrlTitle: htmltext.UrlTitle(info.Title), OriginalText: info.Content, ParsedText: info.Html, Tags: info.Tags, ObjectStatus: info.Status, ObjectShowStatus: info.ShowStatus, SubmitAt: review.CreatedAt.Unix(), SubmitterDisplayName: req.ReviewerMapping[review.Submitter], Reason: review.Reason, } // get user info userInfo, exists, e := cs.userCommon.GetUserBasicInfoByID(ctx, info.ObjectCreatorUserID) if e != nil { log.Errorf("user not found by id: %s, err: %v", info.ObjectCreatorUserID, e) } if exists { _ = copier.Copy(&r.AuthorUserInfo, userInfo) } resp = append(resp, r) } return pager.NewPageModel(total, resp), nil }