Skip to content

Commit

Permalink
feat(question): add question linking feature for closing question
Browse files Browse the repository at this point in the history
  • Loading branch information
LinkinStars committed Oct 18, 2024
1 parent 916dba3 commit 23cc755
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 63 deletions.
2 changes: 1 addition & 1 deletion cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/service/content/question_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
76 changes: 76 additions & 0 deletions internal/service/question_common/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
"math"
"strings"
"time"
Expand Down Expand Up @@ -98,6 +99,7 @@ type QuestionCommon struct {
configService *config.ConfigService
activityQueueService activity_queue.ActivityQueueService
revisionRepo revision.RevisionRepo
siteInfoService siteinfo_common.SiteInfoCommonService
data *data.Data
}

Expand All @@ -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{
Expand All @@ -128,6 +131,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
configService: configService,
activityQueueService: activityQueueService,
revisionRepo: revisionRepo,
siteInfoService: siteInfoService,
data: data,
}
}
Expand Down Expand Up @@ -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
}
124 changes: 62 additions & 62 deletions plugin/importer.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 23cc755

Please sign in to comment.