Merge remote-tracking branch 'github/feat/plugin/search' into feat/1.1.1/state

# Conflicts:
#	internal/repo/answer/answer_repo.go
#	internal/repo/question/question_repo.go
This commit is contained in:
LinkinStars
2023-07-19 15:48:23 +08:00
7 changed files with 260 additions and 6 deletions
+76
View File
@@ -2,6 +2,8 @@ package answer
import (
"context"
"github.com/answerdev/answer/plugin"
"strings"
"time"
"github.com/answerdev/answer/internal/base/constant"
@@ -59,6 +61,7 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
answer.ID = uid.EnShortID(answer.ID)
answer.QuestionID = uid.EnShortID(answer.QuestionID)
}
_ = ar.updateSearch(ctx, answer.ID)
return nil
}
@@ -73,6 +76,7 @@ func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = ar.updateSearch(ctx, answer.ID)
return nil
}
@@ -84,6 +88,7 @@ func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, C
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = ar.updateSearch(ctx, answer.ID)
return err
}
@@ -95,6 +100,7 @@ func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Ans
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = ar.updateSearch(ctx, answer.ID)
return
}
@@ -184,6 +190,7 @@ func (ar *answerRepo) UpdateAccepted(ctx context.Context, id string, questionID
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
}
_ = ar.updateSearch(ctx, id)
return nil
}
@@ -319,3 +326,72 @@ func (ar *answerRepo) AdminSearchList(ctx context.Context, req *schema.AdminAnsw
}
return resp, total, nil
}
// updateSearch update search, if search plugin not enable, do nothing
func (ar *answerRepo) updateSearch(ctx context.Context, answerID string) (err error) {
answerID = uid.DeShortID(answerID)
// check search plugin
var (
s plugin.Search
)
_ = plugin.CallSearch(func(search plugin.Search) error {
s = search
return nil
})
if s == nil {
return
}
answer, exist, err := ar.GetAnswer(ctx, answerID)
if !exist {
return
}
if err != nil {
return err
}
// get question
var (
question *entity.Question
)
exist, err = ar.data.DB.Where("id = ?", answer.QuestionID).Get(&question)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if !exist {
return
}
// get tags
var (
tagListList = make([]*entity.TagRel, 0)
tags = make([]string, 0)
)
st := ar.data.DB.Where("object_id = ?", uid.DeShortID(question.ID))
st.Where("status = ?", entity.TagRelStatusAvailable)
err = st.Find(&tagListList)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
for _, tag := range tagListList {
tags = append(tags, tag.TagID)
}
content := &plugin.SearchContent{
ObjectID: answerID,
Title: question.Title,
Type: "question",
Content: answer.ParsedText,
Answers: 0,
Status: int64(answer.Status),
Tags: tags,
QuesionID: answerID,
UserID: answer.UserID,
Views: int64(question.ViewCount),
Created: answer.CreatedAt.Unix(),
Active: answer.UpdatedAt.Unix(),
Score: int64(answer.VoteCount),
HasAccepted: answer.Accepted == schema.AnswerAcceptedEnable,
}
err = s.UpdateContent(ctx, answerID, content)
return
}
+65
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/segmentfault/pacman/log"
"github.com/answerdev/answer/plugin"
"strings"
"time"
"unicode"
@@ -56,6 +57,7 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
if handler.GetEnableShortID(ctx) {
question.ID = uid.EnShortID(question.ID)
}
_ = qr.updateSearch(ctx, question.ID)
return
}
@@ -79,6 +81,7 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
if handler.GetEnableShortID(ctx) {
question.ID = uid.EnShortID(question.ID)
}
_ = qr.updateSearch(ctx, question.ID)
return
}
@@ -89,6 +92,7 @@ func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionID string) (e
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -100,6 +104,7 @@ func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionID string
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -121,6 +126,7 @@ func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *enti
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -130,6 +136,7 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -148,6 +155,7 @@ func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Que
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -157,6 +165,7 @@ func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Q
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, question.ID)
return nil
}
@@ -407,3 +416,59 @@ func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.Ad
}
return rows, count, nil
}
// updateSearch update search, if search plugin not enable, do nothing
func (qr *questionRepo) updateSearch(ctx context.Context, questionID string) (err error) {
questionID = uid.DeShortID(questionID)
// check search plugin
var (
s plugin.Search
)
_ = plugin.CallSearch(func(search plugin.Search) error {
s = search
return nil
})
if s == nil {
return
}
question, exist, err := qr.GetQuestion(ctx, questionID)
if !exist {
return
}
if err != nil {
return err
}
// get tags
var (
tagListList = make([]*entity.TagRel, 0)
tags = make([]string, 0)
)
session := qr.data.DB.Where("object_id = ?", questionID)
session.Where("status = ?", entity.TagRelStatusAvailable)
err = session.Find(&tagListList)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
for _, tag := range tagListList {
tags = append(tags, tag.TagID)
}
content := &plugin.SearchContent{
ObjectID: questionID,
Title: question.Title,
Type: "question",
Content: question.ParsedText,
Answers: int64(question.AnswerCount),
Status: int64(question.Status),
Tags: tags,
QuesionID: questionID,
UserID: question.UserID,
Views: int64(question.ViewCount),
Created: question.CreatedAt.Unix(),
Active: question.UpdatedAt.Unix(),
Score: int64(question.VoteCount),
HasAccepted: question.AcceptedAnswerID != "" && question.AcceptedAnswerID != "0",
}
err = s.UpdateContent(ctx, questionID, content)
return
}
@@ -3,6 +3,7 @@ package search_common
import (
"context"
"fmt"
"github.com/answerdev/answer/plugin"
"strconv"
"strings"
"time"
@@ -426,6 +427,34 @@ func (sr *searchRepo) parseOrder(ctx context.Context, order string) (res string)
return
}
// ParseSearchPluginResult parse search plugin result
func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResp, err error) {
var (
qres []map[string][]byte
res = make([]map[string][]byte, 0)
b *builder.Builder
)
for _, r := range sres {
switch r.Type {
case "question":
b = builder.MySQL().Select(qFields...).From("question").Where(builder.Eq{"id": r.ID}).
And(builder.Lt{"`status`": entity.QuestionStatusDeleted})
case "answer":
b = builder.MySQL().Select(aFields...).From("answer").LeftJoin("`question`", "`question`.`id` = `answer`.`question_id`").
Where(builder.Eq{"`answer`.`id`": r.ID}).
And(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted}).And(builder.Eq{"`question`.`show`": entity.QuestionShow})
}
qres, err = sr.data.DB.Query(b)
if err != nil || len(qres) == 0 {
continue
}
res = append(res, qres[0])
}
return sr.parseResult(ctx, res)
}
// parseResult parse search result, return the data structure
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) {
for _, r := range res {
var (
+2
View File
@@ -3,10 +3,12 @@ package search_common
import (
"context"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/plugin"
)
type SearchRepo interface {
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResp, err error)
}
+40 -6
View File
@@ -5,6 +5,7 @@ import (
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/search_common"
"github.com/answerdev/answer/internal/service/search_parser"
"github.com/answerdev/answer/plugin"
)
type SearchService struct {
@@ -47,16 +48,49 @@ func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (res
tags,
words := ss.searchParser.ParseStructure(dto)
// check search plugin
var (
s plugin.Search
sres []plugin.SearchResult
)
_ = plugin.CallSearch(func(search plugin.Search) error {
s = search
return nil
})
// search plugin is not found, call system search
if s == nil {
switch searchType {
case "all":
resp, total, err = ss.searchRepo.SearchContents(ctx, words, tags, userID, votes, dto.Page, dto.Size, dto.Order)
if err != nil {
return nil, 0, nil, err
}
case "question":
resp, total, err = ss.searchRepo.SearchQuestions(ctx, words, tags, notAccepted, views, answers, dto.Page, dto.Size, dto.Order)
case "answer":
resp, total, err = ss.searchRepo.SearchAnswers(ctx, words, tags, accepted, questionID, dto.Page, dto.Size, dto.Order)
}
return
}
// call search plugin
switch searchType {
case "all":
resp, total, err = ss.searchRepo.SearchContents(ctx, words, tags, userID, votes, dto.Page, dto.Size, dto.Order)
if err != nil {
return nil, 0, nil, err
}
sres, total, err = s.SearchContents(ctx, words, tags, userID, votes, dto.Page, dto.Size, dto.Order)
case "question":
resp, total, err = ss.searchRepo.SearchQuestions(ctx, words, tags, notAccepted, views, answers, dto.Page, dto.Size, dto.Order)
sres, total, err = s.SearchQuestions(ctx, words, tags, notAccepted, views, answers, dto.Page, dto.Size, dto.Order)
case "answer":
resp, total, err = ss.searchRepo.SearchAnswers(ctx, words, tags, accepted, questionID, dto.Page, dto.Size, dto.Order)
sres, total, err = s.SearchAnswers(ctx, words, tags, accepted, questionID, dto.Page, dto.Size, dto.Order)
}
if err != nil || len(sres) == 0 {
return nil, 0, nil, err
}
// parse search plugin result
resp, err = ss.searchRepo.ParseSearchPluginResult(ctx, sres)
if err != nil {
return nil, 0, nil, err
}
return
}
+4
View File
@@ -56,6 +56,10 @@ func Register(p Base) {
if _, ok := p.(Agent); ok {
registerAgent(p.(Agent))
}
if _, ok := p.(Search); ok {
registerSearch(p.(Search))
}
}
type Stack[T Base] struct {
+44
View File
@@ -0,0 +1,44 @@
package plugin
import (
"context"
)
type SearchResult struct {
// ID content ID
ID string
// Type content type, example: "answer", "question"
Type string
}
type SearchContent struct {
ObjectID string `json:"objectID"`
Title string `json:"title"`
Type string `json:"type"`
Content string `json:"content"`
Answers int64 `json:"answers"`
Status int64 `json:"status"`
Tags []string `json:"tags"`
QuesionID string `json:"questionID"`
UserID string `json:"userID"`
Views int64 `json:"views"`
Created int64 `json:"created"`
Active int64 `json:"active"`
Score int64 `json:"score"`
HasAccepted bool `json:"hasAccepted"`
}
type Search interface {
Base
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (res []SearchResult, total int64, err error)
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (res []SearchResult, total int64, err error)
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (res []SearchResult, total int64, err error)
UpdateContent(ctx context.Context, contentID string, content *SearchContent) error
DeleteContent(ctx context.Context, contentID string) error
}
var (
// CallUserCenter is a function that calls all registered parsers
CallSearch,
registerSearch = MakePlugin[Search](false)
)