feat(question): add question linking feature for closing question
This commit is contained in:
+1
-1
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user