From 811d6de69d6329deef2be5e516fe5848244079ac Mon Sep 17 00:00:00 2001 From: Fen Date: Tue, 13 Aug 2024 10:29:55 +0800 Subject: [PATCH 1/7] docs: enable wiki --- .asf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.asf.yaml b/.asf.yaml index a8baeb21c..7505cd7f9 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -30,7 +30,7 @@ github: - q-and-a - hacktoberfest features: - wiki: false + wiki: true issues: true projects: true discussions: false From 832fa688392a34627f20c6746a9b5afba4621039 Mon Sep 17 00:00:00 2001 From: SantiagoLiendro <126113529+SantiagoLiendro@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:04:27 -0300 Subject: [PATCH 2/7] change in the display of emoticons --- ui/src/pages/Questions/Detail/components/Reactions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx index f1c842951..a98f2fd03 100644 --- a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx @@ -110,7 +110,7 @@ const Index: FC = ({ return (
{showAddCommentBtn && ( From 86cde0e983da102f6bdb4be4ced05967ce40a668 Mon Sep 17 00:00:00 2001 From: shuai Date: Tue, 3 Sep 2024 14:45:13 +0800 Subject: [PATCH 3/7] fix: style --- ui/src/pages/Questions/Detail/components/Reactions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx index a98f2fd03..8fa09509f 100644 --- a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx @@ -110,7 +110,7 @@ const Index: FC = ({ return (
{showAddCommentBtn && ( From eacb79e5120f46267b78c4713358ab7bef9e8d79 Mon Sep 17 00:00:00 2001 From: ferrischi201 Date: Thu, 18 Jul 2024 14:40:03 -0400 Subject: [PATCH 4/7] feat(recommend): add tag-based recommendations in question page Filter out deleted or hidden questions and support multi databases and filter out deleted or hidden questions. For issue:1003. --- cmd/wire_gen.go | 4 +- go.mod | 4 +- go.sum | 9 +- i18n/en_US.yaml | 1 + i18n/no_NO.yaml | 1 + internal/controller/question_controller.go | 15 ++ .../repo/activity_common/activity_repo.go | 13 ++ internal/repo/question/question_repo.go | 51 +++++ internal/repo/repo_test/recommend_test.go | 215 ++++++++++++++++++ internal/router/answer_api_router.go | 1 + internal/schema/question_schema.go | 2 +- internal/service/activity_common/activity.go | 1 + internal/service/content/question_service.go | 53 ++++- internal/service/question_common/question.go | 1 + ui/src/common/interface.ts | 1 + ui/src/components/QuestionList/index.tsx | 6 +- ui/src/pages/Questions/index.tsx | 12 +- ui/src/pages/Tags/Detail/index.tsx | 1 + ui/src/services/client/question.ts | 15 ++ 19 files changed, 391 insertions(+), 15 deletions(-) create mode 100644 internal/repo/repo_test/recommend_test.go diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 4928a122b..31e6d94e1 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -189,12 +189,13 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo) commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware) reportRepo := report.NewReportRepo(dataData, uniqueIDRepo) + tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService) answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService) answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService) externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService, userExternalLoginRepo, siteInfoCommonService) reviewRepo := review.NewReviewRepo(dataData) reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalNotificationQueueService, tagCommonService, questionCommon, notificationQueueService, siteInfoCommonService) - questionService := content.NewQuestionService(questionRepo, answerRepo, tagCommonService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService) + questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService) answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService) reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService) reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService) @@ -202,7 +203,6 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService) voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService) voteController := controller.NewVoteController(voteService, rankService, captchaService) - tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService) tagController := controller.NewTagController(tagService, tagCommonService, rankService) followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo) diff --git a/go.mod b/go.mod index 036a2b42f..8b886a09f 100644 --- a/go.mod +++ b/go.mod @@ -147,8 +147,8 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.23.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.24.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/go.sum b/go.sum index fb49b88b6..ead9468d5 100644 --- a/go.sum +++ b/go.sum @@ -752,21 +752,20 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index a123d70d7..eebfde2a4 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1290,6 +1290,7 @@ ui: newest: Newest active: Active hot: Hot + recommend: Recommend score: Score unanswered: Unanswered modified: modified diff --git a/i18n/no_NO.yaml b/i18n/no_NO.yaml index c91f56b05..859f1ceaf 100644 --- a/i18n/no_NO.yaml +++ b/i18n/no_NO.yaml @@ -859,6 +859,7 @@ ui: newest: Newest active: Active hot: Hot + recommend: Recommend score: Score unanswered: Unanswered modified: modified diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go index 1b0dee92a..725a24afc 100644 --- a/internal/controller/question_controller.go +++ b/internal/controller/question_controller.go @@ -340,6 +340,21 @@ func (qc *QuestionController) QuestionPage(ctx *gin.Context) { handler.HandleResponse(ctx, nil, pager.NewPageModel(total, questions)) } +func (qc *QuestionController) QuestionRecommendPage(ctx *gin.Context) { + req := &schema.QuestionPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx) + + questions, total, err := qc.questionService.GetRecommendQuestionPage(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + handler.HandleResponse(ctx, nil, pager.NewPageModel(total, questions)) +} + // AddQuestion add question // @Summary add question // @Description add question diff --git a/internal/repo/activity_common/activity_repo.go b/internal/repo/activity_common/activity_repo.go index 70f49962c..9f0a59e4d 100644 --- a/internal/repo/activity_common/activity_repo.go +++ b/internal/repo/activity_common/activity_repo.go @@ -107,6 +107,19 @@ func (ar *ActivityRepo) GetActivity(ctx context.Context, session *xorm.Session, return } +func (ar *ActivityRepo) GetUserActivitysByActivityType(ctx context.Context, userID string, activityType int) ( + activityList []*entity.Activity, err error) { + activityList = make([]*entity.Activity, 0) + err = ar.data.DB.Context(ctx).Where("user_id = ?", userID). + And("activity_type = ?", activityType). + And("cancelled = 0"). + Find(&activityList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + func (ar *ActivityRepo) GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error) { sum := &entity.ActivityRankSum{} _, err := ar.data.DB.Context(ctx).Table(entity.Activity{}.TableName()). diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go index 83a841573..aa3694d04 100644 --- a/internal/repo/question/question_repo.go +++ b/internal/repo/question/question_repo.go @@ -401,6 +401,57 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, return questionList, total, err } +// GetRecommendQuestionPageByTags get recommend question page by tags +func (qr *questionRepo) GetRecommendQuestionPageByTags(ctx context.Context, userID string, tagIDs, followedQuestionIDs []string, page, pageSize int) ( + questionList []*entity.Question, total int64, err error) { + questionList = make([]*entity.Question, 0) + orderBySQL := "question.pin DESC, question.created_at DESC" + + // Please Make sure every question has at least one tag + session := qr.data.DB.Context(ctx).Select(entity.Question{}.TableName() + ".*") + + if len(tagIDs) > 0 { + session.Where("question.user_id != ?", userID). + And("question.id NOT IN (SELECT question_id FROM answer WHERE user_id = ?)", userID). + Join("INNER", "tag_rel", "question.id = tag_rel.object_id"). + And("tag_rel.status = ?", entity.TagRelStatusAvailable). + Join("INNER", "tag", "tag.id = tag_rel.tag_id"). + In("tag.id", tagIDs) + } else if len(followedQuestionIDs) == 0 { + return questionList, 0, nil + } + + if len(followedQuestionIDs) > 0 { + idStr := "'" + strings.Join(followedQuestionIDs, "','") + "'" + orderBySQL = fmt.Sprintf("CASE WHEN question.id IN (%s) THEN 0 ELSE 1 END, ", idStr) + orderBySQL + if len(tagIDs) > 0 { + // if tags provided, show followed questions and tag questions + session.Or(builder.In("question.id", followedQuestionIDs)) + } else { + // if no tags, only show followed questions + session.Where(builder.In("question.id", followedQuestionIDs)) + } + } + + session. + And("question.show = ? and question.status = ?", entity.QuestionShow, entity.QuestionStatusAvailable). + Distinct("question.id"). + OrderBy(orderBySQL) + + total, err = pager.Help(page, pageSize, &questionList, &entity.Question{}, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + + if handler.GetEnableShortID(ctx) { + for _, item := range questionList { + item.ID = uid.EnShortID(item.ID) + } + } + + return questionList, total, err +} + func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.AdminQuestionPageReq) ([]*entity.Question, int64, error) { var ( count int64 diff --git a/internal/repo/repo_test/recommend_test.go b/internal/repo/repo_test/recommend_test.go new file mode 100644 index 000000000..6c87dfe74 --- /dev/null +++ b/internal/repo/repo_test/recommend_test.go @@ -0,0 +1,215 @@ +/* + * 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 repo_test + +import ( + "context" + "testing" + + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/repo/activity" + "github.com/apache/incubator-answer/internal/repo/activity_common" + "github.com/apache/incubator-answer/internal/repo/config" + "github.com/apache/incubator-answer/internal/repo/question" + "github.com/apache/incubator-answer/internal/repo/tag" + "github.com/apache/incubator-answer/internal/repo/tag_common" + "github.com/apache/incubator-answer/internal/repo/unique" + "github.com/apache/incubator-answer/internal/repo/user" + config2 "github.com/apache/incubator-answer/internal/service/config" + "github.com/stretchr/testify/assert" +) + +func Test_questionRepo_GetRecommend(t *testing.T) { + var ( + uniqueIDRepo = unique.NewUniqueIDRepo(testDataSource) + questionRepo = question.NewQuestionRepo(testDataSource, uniqueIDRepo) + userRepo = user.NewUserRepo(testDataSource) + tagRelRepo = tag.NewTagRelRepo(testDataSource, uniqueIDRepo) + tagRepo = tag.NewTagRepo(testDataSource, uniqueIDRepo) + tagCommenRepo = tag_common.NewTagCommonRepo(testDataSource, uniqueIDRepo) + configRepo = config.NewConfigRepo(testDataSource) + configService = config2.NewConfigService(configRepo) + activityCommonRepo = activity_common.NewActivityRepo(testDataSource, uniqueIDRepo, configService) + followRepo = activity.NewFollowRepo(testDataSource, uniqueIDRepo, activityCommonRepo) + ) + + // create question and user + user := &entity.User{ + Username: "ferrischi201", + Pass: "ferrischi201", + EMail: "ferrischi201@example.com", + MailStatus: entity.EmailStatusAvailable, + Status: entity.UserStatusAvailable, + DisplayName: "ferrischi201", + IsAdmin: false, + } + err := userRepo.AddUser(context.TODO(), user) + assert.NoError(t, err) + assert.NotEqual(t, "", user.ID) + + questions := make([]*entity.Question, 0) + // tag, unjoin, unfollow + questions = append(questions, &entity.Question{ + UserID: "1", + Title: "Valid recommendation 1", + OriginalText: "A go question", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // tag, unjoin, follow + questions = append(questions, &entity.Question{ + UserID: "1", + Title: "Valid recommendation 2", + OriginalText: "A go question", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // tag, join, unfollow + questions = append(questions, &entity.Question{ + UserID: user.ID, + Title: "Invalid recommendation 1", + OriginalText: "A go question 1", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // tag, join, follow + questions = append(questions, &entity.Question{ + UserID: user.ID, + Title: "Valid recommendation 3", + OriginalText: "A java question", + ParsedText: "Java question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // untag, unjoin, unfollow + questions = append(questions, &entity.Question{ + UserID: "1", + Title: "Invalid recommendation 2", + OriginalText: "A go question", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // untag, unjoin, follow + questions = append(questions, &entity.Question{ + UserID: "1", + Title: "Valid recommendation 4", + OriginalText: "A go question", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // untag, join, unfollow + questions = append(questions, &entity.Question{ + UserID: user.ID, + Title: "Invalid recommendation 3", + OriginalText: "A go question 1", + ParsedText: "Go question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + // untag, join, follow + questions = append(questions, &entity.Question{ + UserID: user.ID, + Title: "Valid recommendation 5", + OriginalText: "A java question", + ParsedText: "Java question", + Status: entity.QuestionStatusAvailable, + Show: entity.QuestionShow, + }) + + for _, question := range questions { + err = questionRepo.AddQuestion(context.TODO(), question) + assert.NoError(t, err) + assert.NotEqual(t, "", question.ID) + } + + tags := []*entity.Tag{ + { + SlugName: "go", + DisplayName: "Golang", + OriginalText: "golang", + ParsedText: "

golang

", + Status: entity.TagStatusAvailable, + }, + { + SlugName: "java", + DisplayName: "Java", + OriginalText: "java", + ParsedText: "

java

", + Status: entity.TagStatusAvailable, + }, + } + err = tagCommenRepo.AddTagList(context.TODO(), tags) + assert.NoError(t, err) + + tagRels := make([]*entity.TagRel, 0) + for i, question := range questions { + tagRel := &entity.TagRel{ + TagID: tags[i/4].ID, + ObjectID: question.ID, + Status: entity.TagRelStatusAvailable, + } + tagRels = append(tagRels, tagRel) + } + err = tagRelRepo.AddTagRelList(context.TODO(), tagRels) + assert.NoError(t, err) + + followQuestionIDs := make([]string, 0) + for i := range questions { + if i%2 == 0 { + continue + } + err = followRepo.Follow(context.TODO(), questions[i].ID, user.ID) + assert.NoError(t, err) + followQuestionIDs = append(followQuestionIDs, questions[i].ID) + } + + // get recommend + questionList, total, err := questionRepo.GetRecommendQuestionPageByTags(context.TODO(), user.ID, []string{tags[0].ID}, followQuestionIDs, 1, 20) + assert.NoError(t, err) + assert.Equal(t, int64(5), total) + assert.Equal(t, 5, len(questionList)) + + // recovery + t.Cleanup(func() { + tagRelIDs := make([]int64, 0) + for i, tagRel := range tagRels { + if i%2 == 1 { + err = followRepo.FollowCancel(context.TODO(), questions[i].ID, user.ID) + assert.NoError(t, err) + } + tagRelIDs = append(tagRelIDs, tagRel.ID) + } + err = tagRelRepo.RemoveTagRelListByIDs(context.TODO(), tagRelIDs) + assert.NoError(t, err) + for _, tag := range tags { + err = tagRepo.RemoveTag(context.TODO(), tag.ID) + assert.NoError(t, err) + } + for _, q := range questions { + err = questionRepo.RemoveQuestion(context.TODO(), q.ID) + assert.NoError(t, err) + } + }) +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 328541868..4434a8d63 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -159,6 +159,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) { r.GET("/question/info", a.questionController.GetQuestion) r.GET("/question/invite", a.questionController.GetQuestionInviteUserInfo) r.GET("/question/page", a.questionController.QuestionPage) + r.GET("/question/recommend/page", a.questionController.QuestionRecommendPage) r.GET("/question/similar/tag", a.questionController.SimilarQuestion) r.GET("/personal/qa/top", a.questionController.UserTop) r.GET("/personal/question/page", a.questionController.PersonalQuestionPage) diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index a4c6ee8c1..2a6e5f122 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -355,7 +355,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" form:"order"` + OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend" 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"` diff --git a/internal/service/activity_common/activity.go b/internal/service/activity_common/activity.go index db565f1e0..6b3557c0d 100644 --- a/internal/service/activity_common/activity.go +++ b/internal/service/activity_common/activity.go @@ -37,6 +37,7 @@ type ActivityRepo interface { GetActivityTypeByObjectType(ctx context.Context, objectKey, action string) (activityType int, err error) GetActivity(ctx context.Context, session *xorm.Session, objectID, userID string, activityType int) ( existsActivity *entity.Activity, exist bool, err error) + GetUserActivitysByActivityType(ctx context.Context, userID string, activityType int) (activityList []*entity.Activity, err error) GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error) GetActivityTypeByConfigKey(ctx context.Context, configKey string) (activityType int, err error) AddActivity(ctx context.Context, activity *entity.Activity) (err error) diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go index 6d8b37ba9..83bd7bedc 100644 --- a/internal/service/content/question_service.go +++ b/internal/service/content/question_service.go @@ -22,7 +22,6 @@ package content import ( "encoding/json" "fmt" - answercommon "github.com/apache/incubator-answer/internal/service/answer_common" "strings" "time" @@ -35,11 +34,13 @@ import ( "github.com/apache/incubator-answer/internal/entity" "github.com/apache/incubator-answer/internal/schema" "github.com/apache/incubator-answer/internal/service/activity" + "github.com/apache/incubator-answer/internal/service/activity_common" "github.com/apache/incubator-answer/internal/service/activity_queue" + answercommon "github.com/apache/incubator-answer/internal/service/answer_common" collectioncommon "github.com/apache/incubator-answer/internal/service/collection_common" "github.com/apache/incubator-answer/internal/service/config" "github.com/apache/incubator-answer/internal/service/export" - "github.com/apache/incubator-answer/internal/service/meta_common" + metacommon "github.com/apache/incubator-answer/internal/service/meta_common" "github.com/apache/incubator-answer/internal/service/notice_queue" "github.com/apache/incubator-answer/internal/service/notification" "github.com/apache/incubator-answer/internal/service/permission" @@ -48,6 +49,7 @@ import ( "github.com/apache/incubator-answer/internal/service/revision_common" "github.com/apache/incubator-answer/internal/service/role" "github.com/apache/incubator-answer/internal/service/siteinfo_common" + "github.com/apache/incubator-answer/internal/service/tag" tagcommon "github.com/apache/incubator-answer/internal/service/tag_common" usercommon "github.com/apache/incubator-answer/internal/service/user_common" "github.com/apache/incubator-answer/pkg/checker" @@ -65,9 +67,11 @@ import ( // QuestionService user service type QuestionService struct { + activityRepo activity_common.ActivityRepo questionRepo questioncommon.QuestionRepo answerRepo answercommon.AnswerRepo tagCommon *tagcommon.TagCommonService + tagService *tag.TagService questioncommon *questioncommon.QuestionCommon userCommon *usercommon.UserCommon userRepo usercommon.UserRepo @@ -87,9 +91,11 @@ type QuestionService struct { } func NewQuestionService( + activityRepo activity_common.ActivityRepo, questionRepo questioncommon.QuestionRepo, answerRepo answercommon.AnswerRepo, tagCommon *tagcommon.TagCommonService, + tagService *tag.TagService, questioncommon *questioncommon.QuestionCommon, userCommon *usercommon.UserCommon, userRepo usercommon.UserRepo, @@ -108,9 +114,11 @@ func NewQuestionService( configService *config.ConfigService, ) *QuestionService { return &QuestionService{ + activityRepo: activityRepo, questionRepo: questionRepo, answerRepo: answerRepo, tagCommon: tagCommon, + tagService: tagService, questioncommon: questioncommon, userCommon: userCommon, userRepo: userRepo, @@ -1348,6 +1356,47 @@ func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.Ques return questions, total, nil } +// GetRecommendQuestionPage retrieves recommended question page based on following tags and questions. +func (qs *QuestionService) GetRecommendQuestionPage(ctx context.Context, req *schema.QuestionPageReq) ( + questions []*schema.QuestionPageResp, total int64, err error) { + followingTagsResp, err := qs.tagService.GetFollowingTags(ctx, req.LoginUserID) + if err != nil { + return nil, 0, err + } + tagIDs := make([]string, 0, len(followingTagsResp)) + for _, tag := range followingTagsResp { + tagIDs = append(tagIDs, tag.TagID) + } + + activityType, err := qs.activityRepo.GetActivityTypeByObjectType(ctx, constant.QuestionObjectType, "follow") + if err != nil { + return nil, 0, err + } + activities, err := qs.activityRepo.GetUserActivitysByActivityType(ctx, req.LoginUserID, activityType) + if err != nil { + return nil, 0, err + } + + followedQuestionIDs := make([]string, 0, len(activities)) + for _, activity := range activities { + if activity.Cancelled == entity.ActivityCancelled { + continue + } + followedQuestionIDs = append(followedQuestionIDs, activity.ObjectID) + } + questionList, total, err := qs.questionRepo.GetRecommendQuestionPageByTags(ctx, req.LoginUserID, tagIDs, followedQuestionIDs, req.Page, req.PageSize) + if err != nil { + return nil, 0, err + } + + questions, err = qs.questioncommon.FormatQuestionsPage(ctx, questionList, req.LoginUserID, "frequent") + if err != nil { + return nil, 0, err + } + + return questions, total, nil +} + func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, req *schema.AdminUpdateQuestionStatusReq) error { setStatus, ok := entity.AdminQuestionSearchStatus[req.Status] if !ok { diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index b9f2fd539..5dab12144 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -57,6 +57,7 @@ type QuestionRepo interface { GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error) GetQuestionPage(ctx context.Context, page, pageSize int, tagIDs []string, userID, orderCond string, inDays int, showHidden, showPending bool) ( questionList []*entity.Question, total int64, err error) + GetRecommendQuestionPageByTags(ctx context.Context, userID string, tagIDs, followedQuestionIDs []string, page, pageSize int) (questionList []*entity.Question, total int64, err error) UpdateQuestionStatus(ctx context.Context, questionID string, status int) (err error) UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error) RecoverQuestion(ctx context.Context, questionID string) (err error) diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index ff7c09027..093680e05 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -288,6 +288,7 @@ export interface LangsType { * @description interface for Question */ export type QuestionOrderBy = + | 'recommend' | 'newest' | 'active' | 'hot' diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index d9b46dd26..c41f4b9dc 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -43,11 +43,13 @@ export const QUESTION_ORDER_KEYS: Type.QuestionOrderBy[] = [ 'hot', 'score', 'unanswered', + 'recommend', ]; interface Props { source: 'questions' | 'tag'; order?: Type.QuestionOrderBy; data; + orderList?: Type.QuestionOrderBy[]; isLoading: boolean; } @@ -55,6 +57,7 @@ const QuestionList: FC = ({ source, order, data, + orderList, isLoading = false, }) => { const { t } = useTranslation('translation', { keyPrefix: 'question' }); @@ -65,6 +68,7 @@ const QuestionList: FC = ({ const curPage = Number(urlSearchParams.get('page')) || 1; const pageSize = 20; const count = data?.count || 0; + const orderKeys = orderList || QUESTION_ORDER_KEYS; return (
@@ -75,7 +79,7 @@ const QuestionList: FC = ({ : t('x_questions', { count })} { page: curPage, order: curOrder as Type.QuestionOrderBy, }; - const { data: listData, isLoading: listLoading } = useQuestionList(reqParams); + const { data: listData, isLoading: listLoading } = + curOrder === 'recommend' + ? useQuestionRecommendList(reqParams) + : useQuestionList(reqParams); const isIndexPage = useMatch('/'); let pageTitle = t('questions', { keyPrefix: 'page_title' }); let slogan = ''; @@ -71,6 +74,11 @@ const Questions: FC = () => { source="questions" data={listData} order={curOrder} + orderList={ + loggedUser.username + ? QUESTION_ORDER_KEYS + : QUESTION_ORDER_KEYS.filter((key) => key !== 'recommend') + } isLoading={listLoading} /> diff --git a/ui/src/pages/Tags/Detail/index.tsx b/ui/src/pages/Tags/Detail/index.tsx index 42b89d5d4..ac2fa937e 100644 --- a/ui/src/pages/Tags/Detail/index.tsx +++ b/ui/src/pages/Tags/Detail/index.tsx @@ -176,6 +176,7 @@ const Index: FC = () => { source="tag" data={listData} order={curOrder} + orderList={QUESTION_ORDER_KEYS.slice(0, 4)} isLoading={listLoading} /> diff --git a/ui/src/services/client/question.ts b/ui/src/services/client/question.ts index 0e382c26f..b62fbf1a7 100644 --- a/ui/src/services/client/question.ts +++ b/ui/src/services/client/question.ts @@ -36,6 +36,21 @@ export const useQuestionList = (params: Type.QueryQuestionsReq) => { }; }; +export const useQuestionRecommendList = (params: Type.QueryQuestionsReq) => { + const apiUrl = `/answer/api/v1/question/recommend/page?${qs.stringify( + params, + )}`; + const { data, error } = useSWR( + [apiUrl], + request.instance.get, + ); + return { + data, + isLoading: !data && !error, + error, + }; +}; + export const useHotQuestions = ( params: Type.QueryQuestionsReq = { page: 1, From cf7b2f0c1a2c8b8f9de7c0d7a692940db702eb62 Mon Sep 17 00:00:00 2001 From: ferrischi201 Date: Tue, 27 Aug 2024 14:07:08 -0400 Subject: [PATCH 5/7] fix(recommend): fix for all database backend --- internal/repo/question/question_repo.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go index aa3694d04..2c6e595ab 100644 --- a/internal/repo/question/question_repo.go +++ b/internal/repo/question/question_repo.go @@ -408,7 +408,13 @@ func (qr *questionRepo) GetRecommendQuestionPageByTags(ctx context.Context, user orderBySQL := "question.pin DESC, question.created_at DESC" // Please Make sure every question has at least one tag - session := qr.data.DB.Context(ctx).Select(entity.Question{}.TableName() + ".*") + selectSQL := entity.Question{}.TableName() + ".*" + if len(followedQuestionIDs) > 0 { + idStr := "'" + strings.Join(followedQuestionIDs, "','") + "'" + selectSQL += fmt.Sprintf(", CASE WHEN question.id IN (%s) THEN 0 ELSE 1 END AS order_priority", idStr) + orderBySQL = "order_priority, " + orderBySQL + } + session := qr.data.DB.Context(ctx).Select(selectSQL) if len(tagIDs) > 0 { session.Where("question.user_id != ?", userID). @@ -422,8 +428,6 @@ func (qr *questionRepo) GetRecommendQuestionPageByTags(ctx context.Context, user } if len(followedQuestionIDs) > 0 { - idStr := "'" + strings.Join(followedQuestionIDs, "','") + "'" - orderBySQL = fmt.Sprintf("CASE WHEN question.id IN (%s) THEN 0 ELSE 1 END, ", idStr) + orderBySQL if len(tagIDs) > 0 { // if tags provided, show followed questions and tag questions session.Or(builder.In("question.id", followedQuestionIDs)) From 8142d78e107149401a4d507d9df44630bd704ffd Mon Sep 17 00:00:00 2001 From: ferrischi201 Date: Tue, 3 Sep 2024 16:02:52 -0400 Subject: [PATCH 6/7] fix(recommend): redirect unauth users from recommend page --- internal/controller/question_controller.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go index 725a24afc..5ddc33f30 100644 --- a/internal/controller/question_controller.go +++ b/internal/controller/question_controller.go @@ -340,6 +340,15 @@ func (qc *QuestionController) QuestionPage(ctx *gin.Context) { handler.HandleResponse(ctx, nil, pager.NewPageModel(total, questions)) } +// QuestionRecommendPage get recommend questions by page +// @Summary get recommend questions by page +// @Description get recommend questions by page +// @Tags Question +// @Accept json +// @Produce json +// @Param data body schema.QuestionPageReq true "QuestionPageReq" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.QuestionPageResp}} +// @Router /answer/api/v1/question/recommend/page [get] func (qc *QuestionController) QuestionRecommendPage(ctx *gin.Context) { req := &schema.QuestionPageReq{} if handler.BindAndCheck(ctx, req) { @@ -347,6 +356,11 @@ func (qc *QuestionController) QuestionRecommendPage(ctx *gin.Context) { } req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx) + if req.LoginUserID == "" { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + return + } + questions, total, err := qc.questionService.GetRecommendQuestionPage(ctx, req) if err != nil { handler.HandleResponse(ctx, err, nil) From cb5a0b7045998d8fcc583b06c7a3f65b0b6cbb59 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Wed, 4 Sep 2024 10:26:57 +0800 Subject: [PATCH 7/7] chore(lint): lint code and regenerate docs --- docs/docs.go | 74 +++++++++++++++++++++++++++++++++++++++++++---- docs/swagger.json | 74 +++++++++++++++++++++++++++++++++++++++++++---- docs/swagger.yaml | 45 ++++++++++++++++++++++++---- go.mod | 3 -- go.sum | 6 ---- 5 files changed, 176 insertions(+), 26 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 376cdf072..cf218a566 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -4185,6 +4185,67 @@ const docTemplate = `{ } } }, + "/answer/api/v1/question/recommend/page": { + "get": { + "description": "get recommend questions by page", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "get recommend questions by page", + "parameters": [ + { + "description": "QuestionPageReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionPageReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/question/recover": { "post": { "security": [ @@ -6586,14 +6647,14 @@ const docTemplate = `{ }, "/custom.css": { "get": { - "description": "get site robots information", + "description": "get site custom CSS", "produces": [ - "application/json" + "text/css" ], "tags": [ "site" ], - "summary": "get site robots information", + "summary": "get site custom CSS", "responses": { "200": { "description": "OK", @@ -8873,7 +8934,8 @@ const docTemplate = `{ "active", "hot", "score", - "unanswered" + "unanswered", + "recommend" ] }, "page": { @@ -10833,8 +10895,8 @@ var SwaggerInfo = &swag.Spec{ Host: "", BasePath: "= \"/\"", Schemes: []string{}, - Title: "\"answer\"", - Description: "= \"answer api\"", + Title: "\"apache answer\"", + Description: "= \"apache answer api\"", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/docs/swagger.json b/docs/swagger.json index 7c103856c..0b24c60c6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,8 +1,8 @@ { "swagger": "2.0", "info": { - "description": "= \"answer api\"", - "title": "\"answer\"", + "description": "= \"apache answer api\"", + "title": "\"apache answer\"", "contact": {}, "version": "= \"v0.0.1\"" }, @@ -4159,6 +4159,67 @@ } } }, + "/answer/api/v1/question/recommend/page": { + "get": { + "description": "get recommend questions by page", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Question" + ], + "summary": "get recommend questions by page", + "parameters": [ + { + "description": "QuestionPageReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionPageReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/question/recover": { "post": { "security": [ @@ -6560,14 +6621,14 @@ }, "/custom.css": { "get": { - "description": "get site robots information", + "description": "get site custom CSS", "produces": [ - "application/json" + "text/css" ], "tags": [ "site" ], - "summary": "get site robots information", + "summary": "get site custom CSS", "responses": { "200": { "description": "OK", @@ -8847,7 +8908,8 @@ "active", "hot", "score", - "unanswered" + "unanswered", + "recommend" ] }, "page": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 88709bed1..eabc45bb9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1416,6 +1416,7 @@ definitions: - hot - score - unanswered + - recommend type: string page: minimum: 1 @@ -2757,8 +2758,8 @@ definitions: type: object info: contact: {} - description: = "answer api" - title: '"answer"' + description: = "apache answer api" + title: '"apache answer"' version: = "v0.0.1" paths: /: @@ -5269,6 +5270,40 @@ paths: summary: get questions by page tags: - Question + /answer/api/v1/question/recommend/page: + get: + consumes: + - application/json + description: get recommend questions by page + parameters: + - description: QuestionPageReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionPageReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.QuestionPageResp' + type: array + type: object + type: object + summary: get recommend questions by page + tags: + - Question /answer/api/v1/question/recover: post: consumes: @@ -6708,15 +6743,15 @@ paths: - Activity /custom.css: get: - description: get site robots information + description: get site custom CSS produces: - - application/json + - text/css responses: "200": description: OK schema: type: string - summary: get site robots information + summary: get site custom CSS tags: - site /installation/base-info: diff --git a/go.mod b/go.mod index 8b886a09f..e14b01b91 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,6 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/containerd/continuity v0.4.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/docker v24.0.6+incompatible // indirect @@ -129,7 +128,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -142,7 +140,6 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - github.com/urfave/cli/v2 v2.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect diff --git a/go.sum b/go.sum index ead9468d5..4bf5cc08b 100644 --- a/go.sum +++ b/go.sum @@ -128,7 +128,6 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -616,7 +615,6 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -692,8 +690,6 @@ github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q= github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= -github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= -github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= @@ -714,9 +710,7 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=