answer/internal/service/question_service.go

1148 lines
36 KiB
Go
Raw Normal View History

2022-09-27 17:59:05 +08:00
package service
import (
"encoding/json"
"fmt"
2022-12-12 18:21:03 +08:00
"math"
2022-11-17 19:09:56 +08:00
"strings"
2022-09-27 17:59:05 +08:00
"time"
"github.com/answerdev/answer/internal/base/constant"
2022-12-12 18:21:03 +08:00
"github.com/answerdev/answer/internal/base/data"
2022-11-30 19:19:01 +08:00
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator"
2022-11-22 18:10:44 +08:00
"github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity"
2022-11-21 19:28:02 +08:00
"github.com/answerdev/answer/internal/service/activity_queue"
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
"github.com/answerdev/answer/internal/service/meta"
"github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/internal/service/permission"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/answerdev/answer/internal/service/revision_common"
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
usercommon "github.com/answerdev/answer/internal/service/user_common"
2022-12-07 17:05:19 +08:00
"github.com/answerdev/answer/pkg/htmltext"
2023-03-09 14:59:07 +08:00
"github.com/answerdev/answer/pkg/uid"
2022-09-27 17:59:05 +08:00
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/i18n"
"github.com/segmentfault/pacman/log"
"golang.org/x/net/context"
)
// QuestionRepo question repository
// QuestionService user service
type QuestionService struct {
questionRepo questioncommon.QuestionRepo
tagCommon *tagcommon.TagCommonService
questioncommon *questioncommon.QuestionCommon
userCommon *usercommon.UserCommon
revisionService *revision_common.RevisionService
metaService *meta.MetaService
collectionCommon *collectioncommon.CollectionCommon
answerActivityService *activity.AnswerActivityService
2022-12-12 18:21:03 +08:00
data *data.Data
2022-09-27 17:59:05 +08:00
}
func NewQuestionService(
questionRepo questioncommon.QuestionRepo,
tagCommon *tagcommon.TagCommonService,
questioncommon *questioncommon.QuestionCommon,
userCommon *usercommon.UserCommon,
revisionService *revision_common.RevisionService,
metaService *meta.MetaService,
collectionCommon *collectioncommon.CollectionCommon,
answerActivityService *activity.AnswerActivityService,
2022-12-12 18:21:03 +08:00
data *data.Data,
2022-09-27 17:59:05 +08:00
) *QuestionService {
return &QuestionService{
questionRepo: questionRepo,
tagCommon: tagCommon,
questioncommon: questioncommon,
userCommon: userCommon,
revisionService: revisionService,
metaService: metaService,
collectionCommon: collectionCommon,
answerActivityService: answerActivityService,
2022-12-12 18:21:03 +08:00
data: data,
2022-09-27 17:59:05 +08:00
}
}
func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQuestionReq) error {
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
if err != nil {
return err
}
if !has {
return nil
}
2022-11-22 16:54:28 +08:00
2022-11-23 11:10:15 +08:00
questionInfo.Status = entity.QuestionStatusClosed
2022-09-27 17:59:05 +08:00
err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo)
if err != nil {
return err
}
closeMeta, _ := json.Marshal(schema.CloseQuestionMeta{
CloseType: req.CloseType,
CloseMsg: req.CloseMsg,
})
err = qs.metaService.AddMeta(ctx, req.ID, entity.QuestionCloseReasonKey, string(closeMeta))
if err != nil {
return err
}
2022-11-21 19:28:02 +08:00
activity_queue.AddActivity(&schema.ActivityMsg{
2022-11-22 19:48:27 +08:00
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionClosed,
2022-11-21 19:28:02 +08:00
})
2022-09-27 17:59:05 +08:00
return nil
}
// ReopenQuestion reopen question
func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.ReopenQuestionReq) error {
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return err
}
if !has {
return nil
}
questionInfo.Status = entity.QuestionStatusAvailable
err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo)
if err != nil {
return err
}
activity_queue.AddActivity(&schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionReopened,
})
return nil
}
2022-09-27 17:59:05 +08:00
// CloseMsgList list close question condition
func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language) (
resp []*schema.GetCloseTypeResp, err error,
) {
2022-09-27 17:59:05 +08:00
resp = make([]*schema.GetCloseTypeResp, 0)
err = json.Unmarshal([]byte(constant.QuestionCloseJSON), &resp)
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
for _, t := range resp {
t.Name = translator.Tr(lang, t.Name)
t.Description = translator.Tr(lang, t.Description)
2022-09-27 17:59:05 +08:00
}
return resp, err
}
func (qs *QuestionService) AddQuestionCheckTags(ctx context.Context, Tags []*entity.Tag) ([]string, error) {
list := make([]string, 0)
for _, tag := range Tags {
if tag.Reserved {
list = append(list, tag.DisplayName)
}
}
if len(list) > 0 {
return list, errors.BadRequest(reason.RequestFormatError)
}
return []string{}, nil
}
2022-12-22 11:40:56 +08:00
func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.QuestionAdd) (errorlist any, err error) {
if len(req.Tags) == 0 {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
2022-12-22 11:40:56 +08:00
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
if err != nil {
return
}
if !recommendExist {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
2022-12-22 11:40:56 +08:00
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
tagNameList := make([]string, 0)
for _, tag := range req.Tags {
tagNameList = append(tagNameList, tag.SlugName)
}
Tags, tagerr := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
if tagerr != nil {
return errorlist, tagerr
}
if !req.QuestionPermission.CanUseReservedTag {
taglist, err := qs.AddQuestionCheckTags(ctx, Tags)
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(taglist, ","))
if err != nil {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
}
return nil, nil
}
2022-09-27 17:59:05 +08:00
// AddQuestion add question
2022-11-28 18:19:22 +08:00
func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo any, err error) {
2022-12-21 15:33:44 +08:00
if len(req.Tags) == 0 {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
2022-12-21 15:33:44 +08:00
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
2022-11-14 17:05:49 +08:00
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
if err != nil {
return
}
2022-11-28 18:19:22 +08:00
if !recommendExist {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
2022-11-28 18:19:22 +08:00
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
tagNameList := make([]string, 0)
for _, tag := range req.Tags {
2023-03-17 17:16:09 +08:00
tag.SlugName = strings.ReplaceAll(tag.SlugName, " ", "-")
tagNameList = append(tagNameList, tag.SlugName)
}
Tags, tagerr := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
if tagerr != nil {
return questionInfo, tagerr
}
if !req.QuestionPermission.CanUseReservedTag {
taglist, err := qs.AddQuestionCheckTags(ctx, Tags)
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(taglist, ","))
if err != nil {
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
}
}
2022-09-27 17:59:05 +08:00
question := &entity.Question{}
now := time.Now()
question.UserID = req.UserID
question.Title = req.Title
question.OriginalText = req.Content
question.ParsedText = req.HTML
2022-09-27 17:59:05 +08:00
question.AcceptedAnswerID = "0"
question.LastAnswerID = "0"
2022-12-02 13:13:04 +08:00
question.LastEditUserID = "0"
2022-11-29 14:56:22 +08:00
//question.PostUpdateTime = nil
2022-09-27 17:59:05 +08:00
question.Status = entity.QuestionStatusAvailable
question.RevisionID = "0"
question.CreatedAt = now
2022-11-29 14:56:22 +08:00
//question.UpdatedAt = nil
2022-09-27 17:59:05 +08:00
err = qs.questionRepo.AddQuestion(ctx, question)
if err != nil {
return
}
objectTagData := schema.TagChange{}
objectTagData.ObjectID = question.ID
2022-09-27 17:59:05 +08:00
objectTagData.Tags = req.Tags
objectTagData.UserID = req.UserID
err = qs.ChangeTag(ctx, &objectTagData)
if err != nil {
return
}
revisionDTO := &schema.AddRevisionDTO{
UserID: question.UserID,
ObjectID: question.ID,
2022-11-21 16:51:13 +08:00
Title: question.Title,
2022-09-27 17:59:05 +08:00
}
2022-11-23 12:07:06 +08:00
questionWithTagsRevision, err := qs.changeQuestionToRevision(ctx, question, Tags)
2022-11-21 16:51:13 +08:00
if err != nil {
return nil, err
}
infoJSON, _ := json.Marshal(questionWithTagsRevision)
revisionDTO.Content = string(infoJSON)
2022-11-22 19:48:27 +08:00
revisionID, err := qs.revisionService.AddRevision(ctx, revisionDTO, true)
2022-09-27 17:59:05 +08:00
if err != nil {
return
}
// user add question count
2022-09-27 17:59:05 +08:00
err = qs.userCommon.UpdateQuestionCount(ctx, question.UserID, 1)
if err != nil {
log.Error("user IncreaseQuestionCount error", err.Error())
}
2022-11-21 19:28:02 +08:00
activity_queue.AddActivity(&schema.ActivityMsg{
2022-11-22 19:48:27 +08:00
UserID: question.UserID,
ObjectID: question.ID,
OriginalObjectID: question.ID,
ActivityTypeKey: constant.ActQuestionAsked,
RevisionID: revisionID,
2022-11-21 19:28:02 +08:00
})
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission)
2022-09-27 17:59:05 +08:00
return
}
// RemoveQuestion delete question
func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.RemoveQuestionReq) (err error) {
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
if err != nil {
return err
}
2023-02-15 15:01:58 +08:00
//if the status is deleted, return directly
if questionInfo.Status == entity.QuestionStatusDeleted {
return nil
}
2022-09-27 17:59:05 +08:00
if !has {
return nil
}
2022-11-22 16:22:30 +08:00
if !req.IsAdmin {
if questionInfo.UserID != req.UserID {
return errors.BadRequest(reason.QuestionCannotDeleted)
}
2022-11-04 16:59:16 +08:00
2022-11-22 16:22:30 +08:00
if questionInfo.AcceptedAnswerID != "0" {
return errors.BadRequest(reason.QuestionCannotDeleted)
}
2022-11-22 18:03:18 +08:00
if questionInfo.AnswerCount > 1 {
2022-11-22 16:22:30 +08:00
return errors.BadRequest(reason.QuestionCannotDeleted)
2022-11-22 16:13:55 +08:00
}
2022-11-22 16:22:30 +08:00
if questionInfo.AnswerCount == 1 {
answersearch := &entity.AnswerSearch{}
answersearch.QuestionID = req.ID
answerList, _, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
if err != nil {
return err
}
for _, answer := range answerList {
if answer.VoteCount > 0 {
return errors.BadRequest(reason.QuestionCannotDeleted)
}
2022-11-22 16:13:55 +08:00
}
}
2022-11-04 16:59:16 +08:00
}
2022-09-27 17:59:05 +08:00
questionInfo.Status = entity.QuestionStatusDeleted
2023-02-20 15:14:39 +08:00
err = qs.questionRepo.UpdateQuestionStatusWithOutUpdateTime(ctx, questionInfo)
2022-09-27 17:59:05 +08:00
if err != nil {
return err
}
// user add question count
2022-09-27 17:59:05 +08:00
err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, -1)
if err != nil {
log.Error("user IncreaseQuestionCount error", err.Error())
}
err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount)
if err != nil {
log.Errorf("user DeleteQuestion rank rollback error %s", err.Error())
}
2022-11-22 19:48:27 +08:00
activity_queue.AddActivity(&schema.ActivityMsg{
2023-03-09 16:41:03 +08:00
UserID: req.UserID,
2022-11-22 19:48:27 +08:00
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionDeleted,
})
2022-09-27 17:59:05 +08:00
return nil
}
2022-12-09 17:52:11 +08:00
func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *schema.QuestionUpdate) (errorlist []*validator.FormErrorField, err error) {
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
if err != nil {
return
}
if !has {
return
}
oldTags, tagerr := qs.tagCommon.GetObjectEntityTag(ctx, req.ID)
if tagerr != nil {
log.Error("GetObjectEntityTag error", tagerr)
return nil, nil
}
tagNameList := make([]string, 0)
oldtagNameList := make([]string, 0)
for _, tag := range req.Tags {
tagNameList = append(tagNameList, tag.SlugName)
}
for _, tag := range oldTags {
oldtagNameList = append(oldtagNameList, tag.SlugName)
}
isChange := qs.tagCommon.CheckTagsIsChange(ctx, tagNameList, oldtagNameList)
//If the content is the same, ignore it
if dbinfo.Title == req.Title && dbinfo.OriginalText == req.Content && !isChange {
return
}
Tags, tagerr := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
if tagerr != nil {
log.Error("GetTagListByNames error", tagerr)
return nil, nil
}
// if user can not use reserved tag, old reserved tag can not be removed and new reserved tag can not be added.
if !req.CanUseReservedTag {
CheckOldTag, CheckNewTag, CheckOldTaglist, CheckNewTaglist := qs.CheckChangeReservedTag(ctx, oldTags, Tags)
if !CheckOldTag {
errMsg := fmt.Sprintf(`The reserved tag "%s" must be present.`,
strings.Join(CheckOldTaglist, ","))
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
return errorlist, err
}
if !CheckNewTag {
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(CheckNewTaglist, ","))
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
return errorlist, err
}
}
return nil, nil
}
2022-09-27 17:59:05 +08:00
// UpdateQuestion update question
2022-11-22 18:10:44 +08:00
func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.QuestionUpdate) (questionInfo any, err error) {
2022-11-24 18:05:56 +08:00
var canUpdate bool
2022-09-27 17:59:05 +08:00
questionInfo = &schema.QuestionInfo{}
2022-11-23 15:19:17 +08:00
_, existUnreviewed, err := qs.revisionService.ExistUnreviewedByObjectID(ctx, req.ID)
if err != nil {
return
2022-11-28 18:19:22 +08:00
2022-11-23 15:19:17 +08:00
}
if existUnreviewed {
err = errors.BadRequest(reason.QuestionCannotUpdate)
return
}
2022-12-01 17:38:37 +08:00
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
if err != nil {
return
}
if !has {
return
}
2023-03-01 17:49:55 +08:00
if dbinfo.Status == entity.QuestionStatusDeleted {
err = errors.BadRequest(reason.QuestionCannotUpdate)
2023-03-02 15:14:56 +08:00
return nil, err
2023-03-01 17:49:55 +08:00
}
2022-12-01 17:38:37 +08:00
2022-09-27 17:59:05 +08:00
now := time.Now()
question := &entity.Question{}
question.Title = req.Title
question.OriginalText = req.Content
question.ParsedText = req.HTML
2023-03-16 16:24:31 +08:00
question.ID = uid.DeShortID(req.ID)
2022-09-27 17:59:05 +08:00
question.UpdatedAt = now
2022-11-29 15:01:17 +08:00
question.PostUpdateTime = now
2022-12-01 17:38:37 +08:00
question.UserID = dbinfo.UserID
question.LastEditUserID = req.UserID
2022-11-23 12:07:06 +08:00
2022-11-28 18:19:22 +08:00
oldTags, tagerr := qs.tagCommon.GetObjectEntityTag(ctx, question.ID)
if tagerr != nil {
return questionInfo, tagerr
}
2022-11-23 12:07:06 +08:00
tagNameList := make([]string, 0)
2022-11-28 18:19:22 +08:00
oldtagNameList := make([]string, 0)
2022-11-23 12:07:06 +08:00
for _, tag := range req.Tags {
2023-03-17 17:16:09 +08:00
tag.SlugName = strings.ReplaceAll(tag.SlugName, " ", "-")
2022-11-23 12:07:06 +08:00
tagNameList = append(tagNameList, tag.SlugName)
}
2022-11-28 18:19:22 +08:00
for _, tag := range oldTags {
oldtagNameList = append(oldtagNameList, tag.SlugName)
}
isChange := qs.tagCommon.CheckTagsIsChange(ctx, tagNameList, oldtagNameList)
//If the content is the same, ignore it
if dbinfo.Title == req.Title && dbinfo.OriginalText == req.Content && !isChange {
return
}
2022-11-23 12:07:06 +08:00
Tags, tagerr := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
if tagerr != nil {
return questionInfo, tagerr
}
// if user can not use reserved tag, old reserved tag can not be removed and new reserved tag can not be added.
if !req.CanUseReservedTag {
2022-12-08 11:44:41 +08:00
CheckOldTag, CheckNewTag, CheckOldTaglist, CheckNewTaglist := qs.CheckChangeReservedTag(ctx, oldTags, Tags)
if !CheckOldTag {
2022-11-28 18:19:22 +08:00
errMsg := fmt.Sprintf(`The reserved tag "%s" must be present.`,
2022-12-08 11:44:41 +08:00
strings.Join(CheckOldTaglist, ","))
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
return errorlist, err
}
if !CheckNewTag {
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(CheckNewTaglist, ","))
2022-11-23 11:38:39 +08:00
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: errMsg,
})
err = errors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
return errorlist, err
}
}
// Check whether mandatory labels are selected
2022-11-22 11:37:37 +08:00
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
if err != nil {
return
}
if !recommendExist {
2022-11-22 18:15:10 +08:00
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
2022-11-22 18:15:10 +08:00
})
err = errors.BadRequest(reason.RecommendTagEnter)
return errorlist, err
2022-11-22 11:37:37 +08:00
}
2022-11-23 12:07:06 +08:00
//Administrators and themselves do not need to be audited
2022-09-27 17:59:05 +08:00
revisionDTO := &schema.AddRevisionDTO{
UserID: question.UserID,
ObjectID: question.ID,
2022-11-21 16:51:13 +08:00
Title: question.Title,
2022-09-27 17:59:05 +08:00
Log: req.EditSummary,
}
2022-11-24 18:05:56 +08:00
if req.NoNeedReview {
2022-11-24 18:05:56 +08:00
canUpdate = true
}
2022-11-23 12:07:06 +08:00
// It's not you or the administrator that needs to be reviewed
2022-11-24 18:05:56 +08:00
if !canUpdate {
2022-11-23 12:07:06 +08:00
revisionDTO.Status = entity.RevisionUnreviewedStatus
} else {
//Direct modification
revisionDTO.Status = entity.RevisionReviewPassStatus
//update question to db
2022-12-01 15:26:20 +08:00
saveerr := qs.questionRepo.UpdateQuestion(ctx, question, []string{"title", "original_text", "parsed_text", "updated_at", "post_update_time", "last_edit_user_id"})
2022-11-23 12:07:06 +08:00
if saveerr != nil {
return questionInfo, saveerr
}
objectTagData := schema.TagChange{}
objectTagData.ObjectID = question.ID
objectTagData.Tags = req.Tags
objectTagData.UserID = req.UserID
tagerr := qs.ChangeTag(ctx, &objectTagData)
if err != nil {
return questionInfo, tagerr
}
}
questionWithTagsRevision, err := qs.changeQuestionToRevision(ctx, question, Tags)
2022-11-23 11:07:28 +08:00
if err != nil {
return nil, err
}
infoJSON, _ := json.Marshal(questionWithTagsRevision)
revisionDTO.Content = string(infoJSON)
2022-11-22 19:48:27 +08:00
revisionID, err := qs.revisionService.AddRevision(ctx, revisionDTO, true)
2022-09-27 17:59:05 +08:00
if err != nil {
return
}
2022-11-24 18:05:56 +08:00
if canUpdate {
2022-11-24 11:29:58 +08:00
activity_queue.AddActivity(&schema.ActivityMsg{
UserID: req.UserID,
ObjectID: question.ID,
ActivityTypeKey: constant.ActQuestionEdited,
RevisionID: revisionID,
OriginalObjectID: question.ID,
})
}
2022-09-27 17:59:05 +08:00
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission)
2022-09-27 17:59:05 +08:00
return
}
// GetQuestion get question one
func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID string,
per schema.QuestionPermission) (resp *schema.QuestionInfo, err error) {
question, err := qs.questioncommon.Info(ctx, questionID, userID)
2022-09-27 17:59:05 +08:00
if err != nil {
return
}
// If the question is deleted, only the administrator and the author can view it
if question.Status == entity.QuestionStatusDeleted && !per.CanReopen && question.UserID != userID {
return nil, errors.NotFound(reason.QuestionNotFound)
}
if question.Status != entity.QuestionStatusClosed {
per.CanReopen = false
}
if question.Status == entity.QuestionStatusClosed {
per.CanClose = false
}
if question.Status == entity.QuestionStatusDeleted {
operation := &schema.Operation{}
operation.Msg = translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionAlreadyDeleted)
operation.Level = schema.OperationLevelDanger
question.Operation = operation
}
2022-12-07 17:05:19 +08:00
question.Description = htmltext.FetchExcerpt(question.HTML, "...", 240)
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen)
2022-09-27 17:59:05 +08:00
return question, nil
}
// GetQuestionAndAddPV get question one
func (qs *QuestionService) GetQuestionAndAddPV(ctx context.Context, questionID, loginUserID string,
per schema.QuestionPermission) (
resp *schema.QuestionInfo, err error) {
2023-02-27 22:11:39 +08:00
err = qs.questioncommon.UpdatePv(ctx, questionID)
if err != nil {
log.Error(err)
}
return qs.GetQuestion(ctx, questionID, loginUserID, per)
}
2022-09-27 17:59:05 +08:00
func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.TagChange) error {
return qs.tagCommon.ObjectChangeTag(ctx, objectTagData)
}
2022-12-08 11:44:41 +08:00
func (qs *QuestionService) CheckChangeReservedTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, bool, []string, []string) {
2022-11-22 11:37:37 +08:00
return qs.tagCommon.CheckChangeReservedTag(ctx, oldobjectTagData, objectTagData)
2022-11-17 19:09:56 +08:00
}
2022-09-27 17:59:05 +08:00
func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order string, page, pageSize int, loginUserID string) ([]*schema.UserQuestionInfo, int64, error) {
userlist := make([]*schema.UserQuestionInfo, 0)
userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName)
if err != nil {
return userlist, 0, err
}
if !Exist {
return userlist, 0, nil
}
search := &schema.QuestionPageReq{}
search.OrderCond = order
2022-09-27 17:59:05 +08:00
search.Page = page
search.PageSize = pageSize
search.UserIDBeSearched = userinfo.ID
search.LoginUserID = loginUserID
questionlist, count, err := qs.GetQuestionPage(ctx, search)
2022-09-27 17:59:05 +08:00
if err != nil {
return userlist, 0, err
}
for _, item := range questionlist {
info := &schema.UserQuestionInfo{}
_ = copier.Copy(info, item)
2022-12-21 16:55:16 +08:00
status, ok := entity.AdminQuestionSearchStatusIntToString[item.Status]
2022-09-30 10:22:41 +08:00
if ok {
info.Status = status
}
2022-09-27 17:59:05 +08:00
userlist = append(userlist, info)
}
return userlist, count, nil
}
func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, order string, page, pageSize int, loginUserID string) ([]*schema.UserAnswerInfo, int64, error) {
answerlist := make([]*schema.AnswerInfo, 0)
userAnswerlist := make([]*schema.UserAnswerInfo, 0)
userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName)
if err != nil {
return userAnswerlist, 0, err
}
if !Exist {
return userAnswerlist, 0, nil
}
answersearch := &entity.AnswerSearch{}
answersearch.UserID = userinfo.ID
2022-09-27 17:59:05 +08:00
answersearch.PageSize = pageSize
answersearch.Page = page
if order == "newest" {
answersearch.Order = entity.AnswerSearchOrderByTime
2022-09-27 17:59:05 +08:00
} else {
answersearch.Order = entity.AnswerSearchOrderByDefault
2022-09-27 17:59:05 +08:00
}
questionIDs := make([]string, 0)
answerList, count, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
if err != nil {
return userAnswerlist, count, err
}
for _, item := range answerList {
answerinfo := qs.questioncommon.AnswerCommon.ShowFormat(ctx, item)
answerlist = append(answerlist, answerinfo)
2023-03-09 14:59:07 +08:00
questionIDs = append(questionIDs, uid.DeShortID(item.QuestionID))
2022-09-27 17:59:05 +08:00
}
questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID)
if err != nil {
return userAnswerlist, count, err
}
2023-03-09 14:59:07 +08:00
2022-09-27 17:59:05 +08:00
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionID]
2022-09-27 17:59:05 +08:00
if ok {
item.QuestionInfo = questionMaps[item.QuestionID]
2022-09-27 17:59:05 +08:00
}
}
for _, item := range answerlist {
info := &schema.UserAnswerInfo{}
_ = copier.Copy(info, item)
info.AnswerID = item.ID
info.QuestionID = item.QuestionID
2022-09-27 17:59:05 +08:00
userAnswerlist = append(userAnswerlist, info)
}
return userAnswerlist, count, nil
}
func (qs *QuestionService) SearchUserCollectionList(ctx context.Context, page, pageSize int, loginUserID string) ([]*schema.QuestionInfo, int64, error) {
list := make([]*schema.QuestionInfo, 0)
userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByID(ctx, loginUserID)
if err != nil {
return list, 0, err
}
if !Exist {
return list, 0, nil
}
collectionSearch := &entity.CollectionSearch{}
collectionSearch.UserID = userinfo.ID
2022-09-27 17:59:05 +08:00
collectionSearch.Page = page
collectionSearch.PageSize = pageSize
collectionlist, count, err := qs.collectionCommon.SearchList(ctx, collectionSearch)
if err != nil {
return list, 0, err
}
questionIDs := make([]string, 0)
for _, item := range collectionlist {
questionIDs = append(questionIDs, item.ObjectID)
}
questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID)
if err != nil {
return list, count, err
}
for _, id := range questionIDs {
2023-03-13 11:01:32 +08:00
_, ok := questionMaps[uid.EnShortID(id)]
2022-09-27 17:59:05 +08:00
if ok {
2023-03-13 11:01:32 +08:00
questionMaps[uid.EnShortID(id)].LastAnsweredUserInfo = nil
questionMaps[uid.EnShortID(id)].UpdateUserInfo = nil
questionMaps[uid.EnShortID(id)].Content = ""
questionMaps[uid.EnShortID(id)].HTML = ""
list = append(list, questionMaps[uid.EnShortID(id)])
2022-09-27 17:59:05 +08:00
}
}
return list, count, nil
}
func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName string, loginUserID string) ([]*schema.UserQuestionInfo, []*schema.UserAnswerInfo, error) {
answerlist := make([]*schema.AnswerInfo, 0)
userAnswerlist := make([]*schema.UserAnswerInfo, 0)
userQuestionlist := make([]*schema.UserQuestionInfo, 0)
userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName)
if err != nil {
return userQuestionlist, userAnswerlist, err
}
if !Exist {
return userQuestionlist, userAnswerlist, nil
}
search := &schema.QuestionPageReq{}
search.OrderCond = "score"
2022-09-27 17:59:05 +08:00
search.Page = 0
search.PageSize = 5
search.UserIDBeSearched = userinfo.ID
search.LoginUserID = loginUserID
questionlist, _, err := qs.GetQuestionPage(ctx, search)
2022-09-27 17:59:05 +08:00
if err != nil {
return userQuestionlist, userAnswerlist, err
}
answersearch := &entity.AnswerSearch{}
answersearch.UserID = userinfo.ID
2022-09-27 17:59:05 +08:00
answersearch.PageSize = 5
answersearch.Order = entity.AnswerSearchOrderByVote
2022-09-27 17:59:05 +08:00
questionIDs := make([]string, 0)
answerList, _, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
if err != nil {
return userQuestionlist, userAnswerlist, err
}
for _, item := range answerList {
answerinfo := qs.questioncommon.AnswerCommon.ShowFormat(ctx, item)
answerlist = append(answerlist, answerinfo)
questionIDs = append(questionIDs, item.QuestionID)
}
questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID)
if err != nil {
return userQuestionlist, userAnswerlist, err
}
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionID]
2022-09-27 17:59:05 +08:00
if ok {
item.QuestionInfo = questionMaps[item.QuestionID]
2022-09-27 17:59:05 +08:00
}
}
for _, item := range questionlist {
info := &schema.UserQuestionInfo{}
_ = copier.Copy(info, item)
2023-03-13 11:12:31 +08:00
info.UrlTitle = htmltext.UrlTitle(info.Title)
2022-09-27 17:59:05 +08:00
userQuestionlist = append(userQuestionlist, info)
}
for _, item := range answerlist {
info := &schema.UserAnswerInfo{}
_ = copier.Copy(info, item)
info.AnswerID = item.ID
info.QuestionID = item.QuestionID
2023-03-13 11:12:31 +08:00
info.QuestionInfo.UrlTitle = htmltext.UrlTitle(info.QuestionInfo.Title)
2022-09-27 17:59:05 +08:00
userAnswerlist = append(userAnswerlist, info)
}
return userQuestionlist, userAnswerlist, nil
}
// SearchByTitleLike
func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string, loginUserID string) ([]*schema.QuestionBaseInfo, error) {
list := make([]*schema.QuestionBaseInfo, 0)
dblist, err := qs.questionRepo.SearchByTitleLike(ctx, title)
if err != nil {
return list, err
}
for _, question := range dblist {
item := &schema.QuestionBaseInfo{}
item.ID = question.ID
item.Title = question.Title
item.ViewCount = question.ViewCount
item.AnswerCount = question.AnswerCount
item.CollectionCount = question.CollectionCount
item.FollowCount = question.FollowCount
2022-12-21 16:55:16 +08:00
status, ok := entity.AdminQuestionSearchStatusIntToString[question.Status]
2022-09-30 10:22:41 +08:00
if ok {
item.Status = status
}
2022-09-27 17:59:05 +08:00
if question.AcceptedAnswerID != "0" {
item.AcceptedAnswer = true
}
list = append(list, item)
}
return list, nil
}
// SimilarQuestion
func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID string, loginUserID string) ([]*schema.QuestionPageResp, int64, error) {
question, err := qs.questioncommon.Info(ctx, questionID, loginUserID)
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, 0, nil
2022-09-27 17:59:05 +08:00
}
tagNames := make([]string, 0, len(question.Tags))
for _, tag := range question.Tags {
2022-09-27 17:59:05 +08:00
tagNames = append(tagNames, tag.SlugName)
}
search := &schema.QuestionPageReq{}
search.OrderCond = "frequent"
2022-09-27 17:59:05 +08:00
search.Page = 0
search.PageSize = 6
2022-10-27 10:41:57 +08:00
if len(tagNames) > 0 {
search.Tag = tagNames[0]
}
search.LoginUserID = loginUserID
return qs.GetQuestionPage(ctx, search)
2022-09-27 17:59:05 +08:00
}
// GetQuestionPage query questions page
func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.QuestionPageReq) (
questions []*schema.QuestionPageResp, total int64, err error) {
questions = make([]*schema.QuestionPageResp, 0)
// query by tag condition
2022-10-27 10:41:57 +08:00
if len(req.Tag) > 0 {
tagInfo, exist, err := qs.tagCommon.GetTagBySlugName(ctx, strings.ToLower(req.Tag))
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, 0, err
2022-09-27 17:59:05 +08:00
}
if exist {
req.TagID = tagInfo.ID
2022-09-27 17:59:05 +08:00
}
}
// query by user condition
if req.Username != "" {
userinfo, exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, 0, err
2022-09-27 17:59:05 +08:00
}
if !exist {
return questions, 0, nil
2022-09-27 17:59:05 +08:00
}
req.UserIDBeSearched = userinfo.ID
2022-09-27 17:59:05 +08:00
}
questionList, total, err := qs.questionRepo.GetQuestionPage(ctx, req.Page, req.PageSize,
req.UserIDBeSearched, req.TagID, req.OrderCond)
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, 0, err
2022-09-27 17:59:05 +08:00
}
questions, err = qs.questioncommon.FormatQuestionsPage(ctx, questionList, req.LoginUserID, req.OrderCond)
2022-09-27 17:59:05 +08:00
if err != nil {
return nil, 0, err
2022-09-27 17:59:05 +08:00
}
return questions, total, nil
2022-09-27 17:59:05 +08:00
}
func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error {
2022-12-21 16:55:16 +08:00
setStatus, ok := entity.AdminQuestionSearchStatus[setStatusStr]
2022-09-27 17:59:05 +08:00
if !ok {
return fmt.Errorf("question status does not exist")
}
questionInfo, exist, err := qs.questionRepo.GetQuestion(ctx, questionID)
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.QuestionNotFound)
}
2022-11-22 19:48:27 +08:00
err = qs.questionRepo.UpdateQuestionStatus(ctx, &entity.Question{ID: questionInfo.ID, Status: setStatus})
2022-09-27 17:59:05 +08:00
if err != nil {
return err
}
if setStatus == entity.QuestionStatusDeleted {
err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount)
if err != nil {
log.Errorf("admin delete question then rank rollback error %s", err.Error())
}
activity_queue.AddActivity(&schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionDeleted,
})
2022-09-27 17:59:05 +08:00
}
2022-11-22 19:48:27 +08:00
if setStatus == entity.QuestionStatusAvailable && questionInfo.Status == entity.QuestionStatusClosed {
activity_queue.AddActivity(&schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionReopened,
2022-11-22 19:48:27 +08:00
})
}
if setStatus == entity.QuestionStatusClosed && questionInfo.Status != entity.QuestionStatusClosed {
activity_queue.AddActivity(&schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionClosed,
})
}
2022-09-27 17:59:05 +08:00
msg := &schema.NotificationMsg{}
msg.ObjectID = questionInfo.ID
msg.Type = schema.NotificationTypeInbox
msg.ReceiverUserID = questionInfo.UserID
msg.TriggerUserID = questionInfo.UserID
msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.YourQuestionWasDeleted
notice_queue.AddNotification(msg)
return nil
}
2022-12-21 16:55:16 +08:00
func (qs *QuestionService) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch, loginUserID string) ([]*schema.AdminQuestionInfo, int64, error) {
2022-09-27 17:59:05 +08:00
list := make([]*schema.AdminQuestionInfo, 0)
2022-12-21 16:55:16 +08:00
status, ok := entity.AdminQuestionSearchStatus[search.StatusStr]
2022-09-27 17:59:05 +08:00
if ok {
search.Status = status
}
if search.Status == 0 {
search.Status = 1
}
2022-12-21 16:55:16 +08:00
dblist, count, err := qs.questionRepo.AdminSearchList(ctx, search)
2022-09-27 17:59:05 +08:00
if err != nil {
return list, count, err
}
userIds := make([]string, 0)
for _, dbitem := range dblist {
item := &schema.AdminQuestionInfo{}
_ = copier.Copy(item, dbitem)
item.CreateTime = dbitem.CreatedAt.Unix()
item.UpdateTime = dbitem.PostUpdateTime.Unix()
item.EditTime = dbitem.UpdatedAt.Unix()
list = append(list, item)
userIds = append(userIds, dbitem.UserID)
}
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
if err != nil {
return list, count, err
}
for _, item := range list {
_, ok = userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserID]
}
}
return list, count, nil
}
2022-12-21 16:55:16 +08:00
// AdminSearchList
func (qs *QuestionService) AdminSearchAnswerList(ctx context.Context, search *entity.AdminAnswerSearch, loginUserID string) ([]*schema.AdminAnswerInfo, int64, error) {
2022-09-27 17:59:05 +08:00
answerlist := make([]*schema.AdminAnswerInfo, 0)
2022-12-21 16:55:16 +08:00
status, ok := entity.AdminAnswerSearchStatus[search.StatusStr]
2022-09-27 17:59:05 +08:00
if ok {
search.Status = status
}
if search.Status == 0 {
search.Status = 1
}
2022-12-21 16:55:16 +08:00
dblist, count, err := qs.questioncommon.AnswerCommon.AdminSearchList(ctx, search)
2022-09-27 17:59:05 +08:00
if err != nil {
return answerlist, count, err
}
questionIDs := make([]string, 0)
userIds := make([]string, 0)
for _, item := range dblist {
answerinfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item)
answerlist = append(answerlist, answerinfo)
questionIDs = append(questionIDs, item.QuestionID)
userIds = append(userIds, item.UserID)
}
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
if err != nil {
return answerlist, count, err
}
questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID)
if err != nil {
return answerlist, count, err
}
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionID]
2022-09-27 17:59:05 +08:00
if ok {
item.QuestionInfo.Title = questionMaps[item.QuestionID].Title
2022-09-27 17:59:05 +08:00
}
_, ok = userInfoMap[item.UserID]
2022-09-27 17:59:05 +08:00
if ok {
item.UserInfo = userInfoMap[item.UserID]
2022-09-27 17:59:05 +08:00
}
}
return answerlist, count, nil
}
2022-11-21 16:51:13 +08:00
2022-11-23 12:07:06 +08:00
func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questionInfo *entity.Question, tags []*entity.Tag) (
2022-11-21 16:51:13 +08:00
questionRevision *entity.QuestionWithTagsRevision, err error) {
questionRevision = &entity.QuestionWithTagsRevision{}
questionRevision.Question = *questionInfo
for _, tag := range tags {
item := &entity.TagSimpleInfoForRevision{}
_ = copier.Copy(item, tag)
questionRevision.Tags = append(questionRevision.Tags, item)
}
return questionRevision, nil
}
2022-12-12 18:21:03 +08:00
func (qs *QuestionService) SitemapCron(ctx context.Context) {
data := &schema.SiteMapList{}
questionNum, err := qs.questionRepo.GetQuestionCount(ctx)
if err != nil {
log.Error("GetQuestionCount error", err)
return
}
if questionNum <= schema.SitemapMaxSize {
questionIDList, err := qs.questionRepo.GetQuestionIDsPage(ctx, 0, int(questionNum))
if err != nil {
log.Error("GetQuestionIDsPage error", err)
return
}
data.QuestionIDs = questionIDList
} else {
nums := make([]int, 0)
totalpages := int(math.Ceil(float64(questionNum) / float64(schema.SitemapMaxSize)))
for i := 1; i <= totalpages; i++ {
siteMapPagedata := &schema.SiteMapPageList{}
nums = append(nums, i)
questionIDList, err := qs.questionRepo.GetQuestionIDsPage(ctx, i, int(schema.SitemapMaxSize))
if err != nil {
log.Error("GetQuestionIDsPage error", err)
return
}
siteMapPagedata.PageData = questionIDList
if setCacheErr := qs.SetCache(ctx, fmt.Sprintf(schema.SitemapPageCachekey, i), siteMapPagedata); setCacheErr != nil {
log.Errorf("set sitemap cron SetCache failed: %s", setCacheErr)
}
}
data.MaxPageNum = nums
}
if setCacheErr := qs.SetCache(ctx, schema.SitemapCachekey, data); setCacheErr != nil {
log.Errorf("set sitemap cron SetCache failed: %s", setCacheErr)
}
}
func (qs *QuestionService) SetCache(ctx context.Context, cachekey string, info interface{}) error {
infoStr, err := json.Marshal(info)
if err != nil {
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
err = qs.data.Cache.SetString(ctx, cachekey, string(infoStr), schema.DashBoardCacheTime)
if err != nil {
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
return nil
}