feat(question): add question linking feature for closing question

This commit is contained in:
LinkinStars
2024-10-18 15:55:31 +08:00
parent 916dba3f06
commit 23cc755aa8
4 changed files with 143 additions and 63 deletions
+1 -1
View File
@@ -177,7 +177,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
answerCommon := answercommon.NewAnswerCommon(answerRepo)
metaRepo := meta.NewMetaRepo(dataData)
metaCommonService := metacommon.NewMetaCommonService(metaRepo)
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, activityQueueService, revisionRepo, dataData)
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, activityQueueService, revisionRepo, siteInfoCommonService, dataData)
eventQueueService := event_queue.NewEventQueueService()
userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventQueueService)
captchaRepo := captcha.NewCaptchaRepo(dataData)
@@ -174,6 +174,9 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
if err != nil {
return err
}
if cf.Key == constant.ReasonADuplicate {
qs.questioncommon.AddQuestionLinkForCloseReason(ctx, questionInfo, req.CloseMsg)
}
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
@@ -199,6 +202,7 @@ func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.Reope
if err != nil {
return err
}
qs.questioncommon.RemoveQuestionLinkForReopen(ctx, questionInfo)
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
@@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
"math"
"strings"
"time"
@@ -98,6 +99,7 @@ type QuestionCommon struct {
configService *config.ConfigService
activityQueueService activity_queue.ActivityQueueService
revisionRepo revision.RevisionRepo
siteInfoService siteinfo_common.SiteInfoCommonService
data *data.Data
}
@@ -113,6 +115,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
configService *config.ConfigService,
activityQueueService activity_queue.ActivityQueueService,
revisionRepo revision.RevisionRepo,
siteInfoService siteinfo_common.SiteInfoCommonService,
data *data.Data,
) *QuestionCommon {
return &QuestionCommon{
@@ -128,6 +131,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
configService: configService,
activityQueueService: activityQueueService,
revisionRepo: revisionRepo,
siteInfoService: siteInfoService,
data: data,
}
}
@@ -795,3 +799,75 @@ func (qs *QuestionCommon) UpdateQuestionLink(ctx context.Context, questionID, an
return parsedText, nil
}
// AddQuestionLinkForCloseReason When the reason about close question is a question link, add the link to the question
func (qs *QuestionCommon) AddQuestionLinkForCloseReason(ctx context.Context,
questionInfo *entity.Question, closeMsg string) {
questionID := qs.tryToGetQuestionIDFromMsg(ctx, closeMsg)
if len(questionID) == 0 {
return
}
linkedQuestion, exist, err := qs.questionRepo.GetQuestion(ctx, questionID)
if err != nil {
log.Errorf("get question error %s", err)
return
}
if !exist {
return
}
err = qs.questionRepo.LinkQuestion(ctx, &entity.QuestionLink{
FromQuestionID: questionInfo.ID,
ToQuestionID: linkedQuestion.ID,
Status: entity.QuestionLinkStatusAvailable,
})
if err != nil {
log.Errorf("link question error %s", err)
}
}
func (qs *QuestionCommon) RemoveQuestionLinkForReopen(ctx context.Context, questionInfo *entity.Question) {
questionInfo.ID = uid.DeShortID(questionInfo.ID)
metaInfo, err := qs.metaCommonService.GetMetaByObjectIdAndKey(ctx, questionInfo.ID, entity.QuestionCloseReasonKey)
if err != nil {
return
}
closeMsgMeta := &schema.CloseQuestionMeta{}
_ = json.Unmarshal([]byte(metaInfo.Value), closeMsgMeta)
linkedQuestionID := qs.tryToGetQuestionIDFromMsg(ctx, closeMsgMeta.CloseMsg)
if len(linkedQuestionID) == 0 {
return
}
err = qs.questionRepo.RemoveQuestionLink(ctx, &entity.QuestionLink{
FromQuestionID: questionInfo.ID,
ToQuestionID: linkedQuestionID,
})
if err != nil {
log.Errorf("remove question link error %s", err)
}
}
func (qs *QuestionCommon) tryToGetQuestionIDFromMsg(ctx context.Context, closeMsg string) (questionID string) {
siteGeneral, err := qs.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Errorf("get site general error %s", err)
return
}
if !strings.HasPrefix(closeMsg, siteGeneral.SiteUrl) {
return
}
// get question id from url
// the url may like: https://xxx.com/questions/D1401/xxx
// the D1401 is question id
questionID = strings.TrimPrefix(closeMsg, siteGeneral.SiteUrl)
questionID = strings.TrimPrefix(questionID, "/questions/")
t := strings.Split(questionID, "/")
if len(t) < 1 {
return ""
}
questionID = t[0]
questionID = uid.DeShortID(questionID)
return questionID
}
+62 -62
View File
@@ -1,62 +1,62 @@
/*
* 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 plugin
import (
"context"
)
type QuestionImporterInfo struct {
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
UserEmail string `json:"user_email"`
}
type Importer interface {
Base
RegisterImporterFunc(ctx context.Context, importer ImporterFunc)
}
type ImporterFunc interface {
AddQuestion(ctx context.Context, questionInfo QuestionImporterInfo) (err error)
}
var (
// CallImporter is a function that calls all registered parsers
CallImporter,
registerImporter = MakePlugin[Importer](false)
)
func ImporterEnabled() (enabled bool) {
_ = CallImporter(func(fn Importer) error {
enabled = true
return nil
})
return
}
func GetImporter() (ip Importer, ok bool) {
_ = CallImporter(func(fn Importer) error {
ip = fn
ok = true
return nil
})
return
}
/*
* 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 plugin
import (
"context"
)
type QuestionImporterInfo struct {
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
UserEmail string `json:"user_email"`
}
type Importer interface {
Base
RegisterImporterFunc(ctx context.Context, importer ImporterFunc)
}
type ImporterFunc interface {
AddQuestion(ctx context.Context, questionInfo QuestionImporterInfo) (err error)
}
var (
// CallImporter is a function that calls all registered parsers
CallImporter,
registerImporter = MakePlugin[Importer](false)
)
func ImporterEnabled() (enabled bool) {
_ = CallImporter(func(fn Importer) error {
enabled = true
return nil
})
return
}
func GetImporter() (ip Importer, ok bool) {
_ = CallImporter(func(fn Importer) error {
ip = fn
ok = true
return nil
})
return
}