From 655a7e48e0168bfc07d6eb0d906cc1de64f5a5e2 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Tue, 19 Nov 2024 18:01:38 +0800 Subject: [PATCH] feat(question): add linked count feature to question --- internal/entity/question_entity.go | 1 + internal/migrations/migrations.go | 1 + internal/migrations/v24.go | 34 +++++++++++++++++ internal/repo/question/question_repo.go | 40 ++++++++++++++++++++ internal/schema/question_schema.go | 4 +- internal/service/question_common/question.go | 23 +++++++++++ 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 internal/migrations/v24.go diff --git a/internal/entity/question_entity.go b/internal/entity/question_entity.go index 1931282c9..9e5dcd112 100644 --- a/internal/entity/question_entity.go +++ b/internal/entity/question_entity.go @@ -73,6 +73,7 @@ type Question struct { LastAnswerID string `xorm:"not null default 0 BIGINT(20) last_answer_id"` PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"` RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` + LinkedCount int `xorm:"not null default 0 INT(11) linked_count"` } // TableName question table name diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index 81a54ae76..00e441985 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -99,6 +99,7 @@ var migrations = []Migration{ NewMigration("v1.3.6", "add hot score to question table", addQuestionHotScore, true), NewMigration("v1.4.0", "add badge/badge_group/badge_award table", addBadges, true), NewMigration("v1.4.1", "add question link", addQuestionLink, true), + NewMigration("v1.4.2", "add the number of question links", addQuestionLinkedCount, false), } func GetMigrations() []Migration { diff --git a/internal/migrations/v24.go b/internal/migrations/v24.go new file mode 100644 index 000000000..015352ae5 --- /dev/null +++ b/internal/migrations/v24.go @@ -0,0 +1,34 @@ +/* + * 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 migrations + +import ( + "context" + "github.com/apache/incubator-answer/internal/entity" + + "xorm.io/xorm" +) + +func addQuestionLinkedCount(ctx context.Context, x *xorm.Engine) error { + type Question struct { + LinkedCount int `xorm:"not null default 0 INT(11) linked_count"` + } + return x.Context(ctx).Sync(new(entity.Question)) +} diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go index 1132501ed..e1d94f2ea 100644 --- a/internal/repo/question/question_repo.go +++ b/internal/repo/question/question_repo.go @@ -410,6 +410,8 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, case "unanswered": session.Where("question.answer_count = 0") session.OrderBy("question.pin desc,question.created_at DESC") + case "frequent": + session.OrderBy("question.pin DESC, question.linked_count DESC") } total, err = pager.Help(page, pageSize, &questionList, &entity.Question{}, session) @@ -708,6 +710,42 @@ func (qr *questionRepo) LinkQuestion(ctx context.Context, link ...*entity.Questi return } +// UpdateQuestionLinkCount update question link count +func (qr *questionRepo) UpdateQuestionLinkCount(ctx context.Context, questionID string) (err error) { + // count the number of links + count, err := qr.data.DB.Context(ctx). + Where("to_question_id = ?", questionID). + Where("status = ?", entity.QuestionLinkStatusAvailable). + Count(&entity.QuestionLink{}) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + + // update the number of links + _, err = qr.data.DB.Context(ctx).ID(questionID). + Cols("linked_count").Update(&entity.Question{LinkedCount: int(count)}) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetLinkedQuestionIDs get linked question ids +func (qr *questionRepo) GetLinkedQuestionIDs(ctx context.Context, questionID string, status int) ( + questionIDs []string, err error) { + questionIDs = make([]string, 0) + err = qr.data.DB.Context(ctx). + Select("to_question_id"). + Table(new(entity.QuestionLink).TableName()). + Where("from_question_id = ?", questionID). + Where("status = ?", status). + Find(&questionIDs) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return questionIDs, nil +} + // RecoverQuestionLink batch recover question link func (qr *questionRepo) RecoverQuestionLink(ctx context.Context, links ...*entity.QuestionLink) (err error) { return qr.UpdateQuestionLinkStatus(ctx, entity.QuestionLinkStatusAvailable, links...) @@ -784,6 +822,8 @@ func (qr *questionRepo) GetQuestionLink(ctx context.Context, page, pageSize int, case "unanswered": session.Where("question.answer_count = 0") session.OrderBy("question.pin desc,question.created_at DESC") + case "frequent": + session.OrderBy("question.pin DESC, question.linked_count DESC") } if page > 0 && pageSize > 0 { diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index de19a8a55..accc3374f 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -356,7 +356,7 @@ const ( type QuestionPageReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1" form:"page_size"` - OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend" form:"order"` + OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend frequent" form:"order"` Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"` InDays int `validate:"omitempty,min=1" form:"in_days"` @@ -504,7 +504,7 @@ type GetQuestionLinkReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1,max=100" form:"page_size"` QuestionID string `validate:"required" form:"question_id"` - OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend" form:"order"` + OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend frequent" form:"order"` InDays int `validate:"omitempty,min=1" form:"in_days"` LoginUserID string `json:"-"` diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 08c74f295..fc01159ec 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -81,6 +81,8 @@ type QuestionRepo interface { RemoveAllUserQuestion(ctx context.Context, userID string) (err error) UpdateSearch(ctx context.Context, questionID string) (err error) LinkQuestion(ctx context.Context, link ...*entity.QuestionLink) (err error) + GetLinkedQuestionIDs(ctx context.Context, questionID string, status int) (questionIDs []string, err error) + UpdateQuestionLinkCount(ctx context.Context, questionID string) (err error) RemoveQuestionLink(ctx context.Context, link ...*entity.QuestionLink) (err error) RecoverQuestionLink(ctx context.Context, link ...*entity.QuestionLink) (err error) UpdateQuestionLinkStatus(ctx context.Context, status int, links ...*entity.QuestionLink) (err error) @@ -704,6 +706,17 @@ func (qs *QuestionCommon) UpdateQuestionLink(ctx context.Context, questionID, an if err != nil { return parsedText, err } + // Update the number of question links that have been removed + linkedQuestionIDs, err := qs.questionRepo.GetLinkedQuestionIDs(ctx, questionID, entity.QuestionLinkStatusDeleted) + if err != nil { + log.Errorf("get linked question ids error %v", err) + } else { + for _, id := range linkedQuestionIDs { + if err := qs.questionRepo.UpdateQuestionLinkCount(ctx, id); err != nil { + log.Errorf("update question link count error %v", err) + } + } + } links := checker.GetQuestionLink(originalText) if len(links) == 0 { @@ -799,6 +812,16 @@ func (qs *QuestionCommon) UpdateQuestionLink(ctx context.Context, questionID, an } } + // update question linked count + for _, link := range validLinks { + if len(link.ToQuestionID) == 0 { + continue + } + if err := qs.questionRepo.UpdateQuestionLinkCount(ctx, link.ToQuestionID); err != nil { + log.Errorf("update question link count error %v", err) + } + } + return parsedText, nil }