This is an automated email from the ASF dual-hosted git repository. linkinstar pushed a commit to branch feat/1.4.0/badges in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit 07e4f549b13216f76a69a897fb73d2a8fcd55de4 Author: LinkinStars <[email protected]> AuthorDate: Thu Aug 8 16:19:47 2024 +0800 feat(badge): add event for the badge --- cmd/wire_gen.go | 18 +++--- internal/base/constant/event.go | 75 ++++++++++++++++++++++ internal/controller/template_controller.go | 16 +++++ internal/repo/badge/badge_event_handler.go | 57 +++++++++++++++++ internal/repo/badge/badge_rule.go | 95 ++++++++++++++++++++++++++++ internal/repo/badge/event_rule_mapping.go | 47 ++++++++++++++ internal/repo/badge/rule.go | 43 +++++++++++++ internal/schema/event_schema.go | 70 ++++++++++++++++++++ internal/service/comment/comment_service.go | 19 +++++- internal/service/content/answer_service.go | 13 ++++ internal/service/content/question_service.go | 10 +++ internal/service/content/user_service.go | 8 +++ internal/service/content/vote_service.go | 31 +++++++++ internal/service/event_queue/event_queue.go | 69 ++++++++++++++++++++ internal/service/meta/meta_service.go | 22 +++++-- internal/service/provider.go | 2 + internal/service/report/report_service.go | 30 ++++++++- 17 files changed, 611 insertions(+), 14 deletions(-) diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 4928a122..c6cdc021 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -78,6 +78,7 @@ import ( config2 "github.com/apache/incubator-answer/internal/service/config" "github.com/apache/incubator-answer/internal/service/content" "github.com/apache/incubator-answer/internal/service/dashboard" + "github.com/apache/incubator-answer/internal/service/event_queue" export2 "github.com/apache/incubator-answer/internal/service/export" "github.com/apache/incubator-answer/internal/service/follow" meta2 "github.com/apache/incubator-answer/internal/service/meta" @@ -172,7 +173,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, metaRepo := meta.NewMetaRepo(dataData) metaCommonService := metacommon.NewMetaCommonService(metaRepo) questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, activityQueueService, revisionRepo, dataData) - userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon) + eventQueueService := event_queue.NewEventQueueService() + userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventQueueService) captchaRepo := captcha.NewCaptchaRepo(dataData) captchaService := action.NewCaptchaService(captchaRepo) userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService, userNotificationConfigService) @@ -181,7 +183,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService) notificationQueueService := notice_queue.NewNotificationQueueService() externalNotificationQueueService := notice_queue.NewNewQuestionNotificationQueueService() - commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService) + commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService, eventQueueService) rolePowerRelRepo := role.NewRolePowerRelRepo(dataData) rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService) rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService) @@ -194,13 +196,13 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, 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) - answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService) + questionService := content.NewQuestionService(questionRepo, answerRepo, tagCommonService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService, eventQueueService) + answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService, eventQueueService) reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService) - reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService) + reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventQueueService) reportController := controller.NewReportController(reportService, rankService, captchaService) contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService) - voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService) + voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, eventQueueService) voteController := controller.NewVoteController(voteService, rankService, captchaService) tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService) tagController := controller.NewTagController(tagService, tagCommonService, rankService) @@ -251,7 +253,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, permissionController := controller.NewPermissionController(rankService) userPluginController := controller.NewUserPluginController(pluginCommonService) reviewController := controller.NewReviewController(reviewService, rankService, captchaService) - metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo) + metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, eventQueueService) metaController := controller.NewMetaController(metaService) answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginCon [...] swaggerRouter := router.NewSwaggerRouter(swaggerConf) @@ -260,7 +262,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService) shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService) templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo) - templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService) + templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, eventQueueService, userService) templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController, authUserMiddleware) connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService) userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService) diff --git a/internal/base/constant/event.go b/internal/base/constant/event.go new file mode 100644 index 00000000..f7fd8412 --- /dev/null +++ b/internal/base/constant/event.go @@ -0,0 +1,75 @@ +/* + * 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 constant + +// EventType event type. It is used to define the type of event. Such as object.action +type EventType string + +// event object +const ( + eventQuestion = "question" + eventAnswer = "answer" + eventComment = "comment" + eventUser = "user" +) + +// event action +const ( + eventCreate = "create" + eventUpdate = "update" + eventDelete = "delete" + eventVote = "vote" + eventAccept = "accept" // only question have the accept event + eventShare = "share" // the object share link has been clicked + eventFlag = "flag" + eventReact = "react" +) + +const ( + EventUserUpdate EventType = eventUser + "." + eventUpdate + EventUserShare EventType = eventUser + "." + eventShare +) + +const ( + EventQuestionCreate EventType = eventQuestion + "." + eventCreate + EventQuestionUpdate EventType = eventQuestion + "." + eventUpdate + EventQuestionDelete EventType = eventQuestion + "." + eventDelete + EventQuestionVote EventType = eventQuestion + "." + eventVote + EventQuestionAccept EventType = eventQuestion + "." + eventAccept + EventQuestionFlag EventType = eventQuestion + "." + eventFlag + EventQuestionReact EventType = eventQuestion + "." + eventReact +) + +const ( + EventAnswerCreate EventType = eventAnswer + "." + eventCreate + EventAnswerUpdate EventType = eventAnswer + "." + eventUpdate + EventAnswerDelete EventType = eventAnswer + "." + eventDelete + EventAnswerVote EventType = eventAnswer + "." + eventVote + EventAnswerFlag EventType = eventAnswer + "." + eventFlag + EventAnswerReact EventType = eventAnswer + "." + eventReact +) + +const ( + EventCommentCreate EventType = eventComment + "." + eventCreate + EventCommentUpdate EventType = eventComment + "." + eventUpdate + EventCommentDelete EventType = eventComment + "." + eventDelete + EventCommentVote EventType = eventComment + "." + eventVote + EventCommentFlag EventType = eventComment + "." + eventFlag +) diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index 09f5bffc..a786f646 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -22,6 +22,8 @@ package controller import ( "encoding/json" "fmt" + "github.com/apache/incubator-answer/internal/service/content" + "github.com/apache/incubator-answer/internal/service/event_queue" "github.com/apache/incubator-answer/plugin" "html/template" "net/http" @@ -54,12 +56,16 @@ type TemplateController struct { cssPath string templateRenderController *templaterender.TemplateRenderController siteInfoService siteinfo_common.SiteInfoCommonService + eventQueueService event_queue.EventQueueService + userService *content.UserService } // NewTemplateController new controller func NewTemplateController( templateRenderController *templaterender.TemplateRenderController, siteInfoService siteinfo_common.SiteInfoCommonService, + eventQueueService event_queue.EventQueueService, + userService *content.UserService, ) *TemplateController { script, css := GetStyle() return &TemplateController{ @@ -67,6 +73,8 @@ func NewTemplateController( cssPath: css, templateRenderController: templateRenderController, siteInfoService: siteInfoService, + eventQueueService: eventQueueService, + userService: userService, } } func GetStyle() (script []string, css string) { @@ -271,6 +279,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) { id := ctx.Param("id") title := ctx.Param("title") answerid := ctx.Param("answerid") + shareUsername := ctx.Query("share") if checker.IsQuestionsIgnorePath(id) { // if id == "ask" { file, err := ui.Build.ReadFile("build/index.html") @@ -291,6 +300,13 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) { tc.Page404(ctx) return } + if len(shareUsername) > 0 { + userInfo, err := tc.userService.GetOtherUserInfoByUsername( + ctx, &schema.GetOtherUserInfoByUsernameReq{Username: shareUsername}) + if err == nil { + tc.eventQueueService.Send(ctx, schema.NewEvent(constant.EventUserShare, userInfo.ID)) + } + } encodeTitle := htmltext.UrlTitle(detail.Title) if encodeTitle == title { correctTitle = true diff --git a/internal/repo/badge/badge_event_handler.go b/internal/repo/badge/badge_event_handler.go new file mode 100644 index 00000000..242778ec --- /dev/null +++ b/internal/repo/badge/badge_event_handler.go @@ -0,0 +1,57 @@ +/* + * 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 badge + +import ( + "context" + "github.com/apache/incubator-answer/internal/service/event_queue" + "github.com/segmentfault/pacman/log" + + "github.com/apache/incubator-answer/internal/base/data" + "github.com/apache/incubator-answer/internal/schema" +) + +type BadgeEventService struct { + data *data.Data + eventQueueService event_queue.EventQueueService +} + +func NewBadgeEventService( + data *data.Data, + eventQueueService event_queue.EventQueueService, +) *BadgeEventService { + n := &BadgeEventService{ + data: data, + eventQueueService: eventQueueService, + } + eventQueueService.RegisterHandler(n.Handler) + return n +} + +func (ns *BadgeEventService) Handler(ctx context.Context, msg *schema.EventMsg) error { + log.Debugf("received badge event %+v", msg) + // TODO: Check if badge already exists + + // TODO: Check rule + + // TODO: Distribute badge + + return nil +} diff --git a/internal/repo/badge/badge_rule.go b/internal/repo/badge/badge_rule.go new file mode 100644 index 00000000..3ae0b7b4 --- /dev/null +++ b/internal/repo/badge/badge_rule.go @@ -0,0 +1,95 @@ +/* + * 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 badge + +import ( + "context" + "github.com/apache/incubator-answer/internal/base/data" + "github.com/apache/incubator-answer/internal/base/reason" + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// BadgeRuleRepo collection repository +type BadgeRuleRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// FilledPersonalProfile filled personal profile +func (br *BadgeRuleRepo) FilledPersonalProfile(ctx context.Context, userID string) (reach bool, err error) { + bean := &entity.User{ID: userID} + exist, err := br.data.DB.Context(ctx).Get(bean) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if !exist { + return false, nil + } + if len(bean.Bio) > 0 { + return true, nil + } + return false, nil +} + +// FirstPostEdit first post edit +func (br *BadgeRuleRepo) FirstPostEdit(ctx context.Context, userID string, objectID string) { + +} + +// FirstFlaggedPost first flagged post. +func (br *BadgeRuleRepo) FirstFlaggedPost(ctx context.Context, userID string, reportID string) { +} + +// FirstVotedPost first voted post +func (br *BadgeRuleRepo) FirstVotedPost(ctx context.Context) { + +} + +// FirstReactedPost first reacted post +func (br *BadgeRuleRepo) FirstReactedPost(ctx context.Context) { + +} + +// FirstSharedPost first shared post +func (br *BadgeRuleRepo) FirstSharedPost(ctx context.Context) { + +} + +// AskQuestionAcceptAnswer ask question accept answer +func (br *BadgeRuleRepo) AskQuestionAcceptAnswer(ctx context.Context) { + +} + +// AnswerAccepted answer accepted +func (br *BadgeRuleRepo) AnswerAccepted(ctx context.Context) { + +} + +// ReachAnswerScore reach answer score +func (br *BadgeRuleRepo) ReachAnswerScore(ctx context.Context) { + +} + +// ReachQuestionScore reach question score +func (br *BadgeRuleRepo) ReachQuestionScore(ctx context.Context) { + +} diff --git a/internal/repo/badge/event_rule_mapping.go b/internal/repo/badge/event_rule_mapping.go new file mode 100644 index 00000000..8f01fdef --- /dev/null +++ b/internal/repo/badge/event_rule_mapping.go @@ -0,0 +1,47 @@ +/* + * 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 badge + +import "github.com/apache/incubator-answer/internal/base/constant" + +var ( + EventRuleMapping = map[constant.EventType][]string{ + constant.EventUserUpdate: {FilledPersonalProfile}, + constant.EventUserShare: {FirstSharedPost}, + constant.EventQuestionCreate: {""}, + constant.EventQuestionUpdate: {FirstPostEdit}, + constant.EventQuestionDelete: {""}, + constant.EventQuestionVote: {FirstVotedPost, ReachQuestionScore}, + constant.EventQuestionAccept: {AskQuestionAcceptAnswer, AnswerAccepted}, + constant.EventQuestionFlag: {FirstFlaggedPost}, + constant.EventQuestionReact: {FirstReactedPost}, + constant.EventAnswerCreate: {""}, + constant.EventAnswerUpdate: {FirstPostEdit}, + constant.EventAnswerDelete: {""}, + constant.EventAnswerVote: {FirstVotedPost, ReachAnswerScore}, + constant.EventAnswerFlag: {FirstFlaggedPost}, + constant.EventAnswerReact: {FirstReactedPost}, + constant.EventCommentCreate: {""}, + constant.EventCommentUpdate: {""}, + constant.EventCommentDelete: {""}, + constant.EventCommentVote: {FirstVotedPost}, + constant.EventCommentFlag: {FirstFlaggedPost}, + } +) diff --git a/internal/repo/badge/rule.go b/internal/repo/badge/rule.go new file mode 100644 index 00000000..635b30da --- /dev/null +++ b/internal/repo/badge/rule.go @@ -0,0 +1,43 @@ +/* + * 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 badge + +const ( + // FilledPersonalProfile filled personal profile + FilledPersonalProfile = "filled_personal_profile" + // FirstPostEdit first post edit + FirstPostEdit = "first_post_edit" + // FirstFlaggedPost first flagged post. + FirstFlaggedPost = "first_flagged_post" + // FirstVotedPost first voted post + FirstVotedPost = "first_voted_post" + // FirstReactedPost first reacted post + FirstReactedPost = "first_reacted_post" + // FirstSharedPost first shared post + FirstSharedPost = "first_shared_post" + // AskQuestionAcceptAnswer ask question accept answer + AskQuestionAcceptAnswer = "ask_question_accept_answer" + // AnswerAccepted answer accepted + AnswerAccepted = "answer_accepted" + // ReachAnswerScore reach answer score + ReachAnswerScore = "reach_answer_score" + // ReachQuestionScore reach question score + ReachQuestionScore = "reach_question_score" +) diff --git a/internal/schema/event_schema.go b/internal/schema/event_schema.go new file mode 100644 index 00000000..01f80fbb --- /dev/null +++ b/internal/schema/event_schema.go @@ -0,0 +1,70 @@ +/* + * 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 schema + +import "github.com/apache/incubator-answer/internal/base/constant" + +// EventMsg event message +type EventMsg struct { + EventType constant.EventType + UserID string + + QuestionID string + QuestionUserID string + + AnswerID string + AnswerUserID string + + CommentID string + CommentUserID string + + ExtraInfo map[string]string +} + +func NewEvent(e constant.EventType, userID string) *EventMsg { + return &EventMsg{ + UserID: userID, + EventType: e, + ExtraInfo: make(map[string]string), + } +} + +func (e *EventMsg) QID(questionID, userID string) *EventMsg { + e.QuestionID = questionID + e.QuestionUserID = userID + return e +} + +func (e *EventMsg) AID(answerID, userID string) *EventMsg { + e.AnswerID = answerID + e.AnswerUserID = userID + return e +} + +func (e *EventMsg) CID(comment, userID string) *EventMsg { + e.CommentID = comment + e.CommentUserID = userID + return e +} + +func (e *EventMsg) AddExtra(key, value string) *EventMsg { + e.ExtraInfo[key] = value + return e +} diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go index 9f2c45f1..75d30f9d 100644 --- a/internal/service/comment/comment_service.go +++ b/internal/service/comment/comment_service.go @@ -21,6 +21,7 @@ package comment import ( "context" + "github.com/apache/incubator-answer/internal/service/event_queue" "time" "github.com/apache/incubator-answer/internal/base/constant" @@ -86,6 +87,7 @@ type CommentService struct { notificationQueueService notice_queue.NotificationQueueService externalNotificationQueueService notice_queue.ExternalNotificationQueueService activityQueueService activity_queue.ActivityQueueService + eventQueueService event_queue.EventQueueService } // NewCommentService new comment service @@ -100,6 +102,7 @@ func NewCommentService( notificationQueueService notice_queue.NotificationQueueService, externalNotificationQueueService notice_queue.ExternalNotificationQueueService, activityQueueService activity_queue.ActivityQueueService, + eventQueueService event_queue.EventQueueService, ) *CommentService { return &CommentService{ commentRepo: commentRepo, @@ -112,6 +115,7 @@ func NewCommentService( notificationQueueService: notificationQueueService, externalNotificationQueueService: externalNotificationQueueService, activityQueueService: activityQueueService, + eventQueueService: eventQueueService, } } @@ -184,13 +188,19 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment OriginalObjectID: req.ObjectID, ActivityTypeKey: constant.ActQuestionCommented, } + var event *schema.EventMsg switch objInfo.ObjectType { case constant.QuestionObjectType: activityMsg.ActivityTypeKey = constant.ActQuestionCommented + event = schema.NewEvent(constant.EventCommentCreate, req.UserID). + CID(comment.ID, comment.UserID).QID(objInfo.QuestionID, objInfo.ObjectCreatorUserID) case constant.AnswerObjectType: activityMsg.ActivityTypeKey = constant.ActAnswerCommented + event = schema.NewEvent(constant.EventCommentCreate, req.UserID). + CID(comment.ID, comment.UserID).AID(objInfo.AnswerID, objInfo.ObjectCreatorUserID) } cs.activityQueueService.Send(ctx, activityMsg) + cs.eventQueueService.Send(ctx, event) return resp, nil } @@ -241,7 +251,12 @@ func (cs *CommentService) addCommentNotification( // RemoveComment delete comment func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveCommentReq) (err error) { - return cs.commentRepo.RemoveComment(ctx, req.CommentID) + err = cs.commentRepo.RemoveComment(ctx, req.CommentID) + if err != nil { + return err + } + cs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventCommentDelete, req.UserID).CID(req.CommentID, req.UserID)) + return nil } // UpdateComment update comment @@ -273,6 +288,8 @@ func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateC OriginalText: req.OriginalText, ParsedText: req.ParsedText, } + cs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventCommentUpdate, req.UserID). + CID(old.ID, old.UserID)) return resp, nil } diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go index f8feda8a..5674a79b 100644 --- a/internal/service/content/answer_service.go +++ b/internal/service/content/answer_service.go @@ -22,6 +22,7 @@ package content import ( "context" "encoding/json" + "github.com/apache/incubator-answer/internal/service/event_queue" "time" "github.com/apache/incubator-answer/internal/base/constant" @@ -67,6 +68,7 @@ type AnswerService struct { externalNotificationQueueService notice_queue.ExternalNotificationQueueService activityQueueService activity_queue.ActivityQueueService reviewService *review.ReviewService + eventQueueService event_queue.EventQueueService } func NewAnswerService( @@ -86,6 +88,7 @@ func NewAnswerService( externalNotificationQueueService notice_queue.ExternalNotificationQueueService, activityQueueService activity_queue.ActivityQueueService, reviewService *review.ReviewService, + eventQueueService event_queue.EventQueueService, ) *AnswerService { return &AnswerService{ answerRepo: answerRepo, @@ -104,6 +107,7 @@ func NewAnswerService( externalNotificationQueueService: externalNotificationQueueService, activityQueueService: activityQueueService, reviewService: reviewService, + eventQueueService: eventQueueService, } } @@ -175,6 +179,8 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns OriginalObjectID: answerInfo.ID, ActivityTypeKey: constant.ActAnswerDeleted, }) + as.eventQueueService.Send(ctx, schema.NewEvent(constant.EventAnswerDelete, req.UserID). + AID(answerInfo.ID, answerInfo.UserID)) return } @@ -295,6 +301,8 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) ( OriginalObjectID: questionInfo.ID, ActivityTypeKey: constant.ActQuestionAnswered, }) + as.eventQueueService.Send(ctx, schema.NewEvent(constant.EventAnswerCreate, req.UserID). + AID(insertData.ID, insertData.UserID)) return insertData.ID, nil } @@ -383,6 +391,8 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq ActivityTypeKey: constant.ActAnswerEdited, RevisionID: revisionID, }) + as.eventQueueService.Send(ctx, schema.NewEvent(constant.EventAnswerUpdate, req.UserID). + AID(insertData.ID, insertData.UserID)) } return insertData.ID, nil @@ -436,6 +446,9 @@ func (as *AnswerService) AcceptAnswer(ctx context.Context, req *schema.AcceptAns oldAnswerInfo.ID = uid.DeShortID(oldAnswerInfo.ID) } + as.eventQueueService.Send(ctx, schema.NewEvent(constant.EventQuestionAccept, req.UserID). + QID(questionInfo.ID, questionInfo.UserID).AID(req.AnswerID, req.UserID)) + as.updateAnswerRank(ctx, req.UserID, questionInfo, acceptedAnswerInfo, oldAnswerInfo) return nil } diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go index 6d8b37ba..dfb778ea 100644 --- a/internal/service/content/question_service.go +++ b/internal/service/content/question_service.go @@ -23,6 +23,7 @@ import ( "encoding/json" "fmt" answercommon "github.com/apache/incubator-answer/internal/service/answer_common" + "github.com/apache/incubator-answer/internal/service/event_queue" "strings" "time" @@ -84,6 +85,7 @@ type QuestionService struct { newQuestionNotificationService *notification.ExternalNotificationService reviewService *review.ReviewService configService *config.ConfigService + eventQueueService event_queue.EventQueueService } func NewQuestionService( @@ -106,6 +108,7 @@ func NewQuestionService( newQuestionNotificationService *notification.ExternalNotificationService, reviewService *review.ReviewService, configService *config.ConfigService, + eventQueueService event_queue.EventQueueService, ) *QuestionService { return &QuestionService{ questionRepo: questionRepo, @@ -127,6 +130,7 @@ func NewQuestionService( newQuestionNotificationService: newQuestionNotificationService, reviewService: reviewService, configService: configService, + eventQueueService: eventQueueService, } } @@ -385,6 +389,8 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question qs.externalNotificationQueueService.Send(ctx, schema.CreateNewQuestionNotificationMsg(question.ID, question.Title, question.UserID, tags)) } + qs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventQuestionCreate, req.UserID). + QID(question.ID, question.UserID)) questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission) return @@ -546,6 +552,8 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov OriginalObjectID: questionInfo.ID, ActivityTypeKey: constant.ActQuestionDeleted, }) + qs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventQuestionDelete, req.UserID). + QID(questionInfo.ID, questionInfo.UserID)) return nil } @@ -937,6 +945,8 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest RevisionID: revisionID, OriginalObjectID: question.ID, }) + qs.eventQueueService.Send(ctx, schema.NewEvent(constant.EventQuestionUpdate, req.UserID). + QID(question.ID, question.UserID)) } questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission) diff --git a/internal/service/content/user_service.go b/internal/service/content/user_service.go index 11f3bb63..2a6122ae 100644 --- a/internal/service/content/user_service.go +++ b/internal/service/content/user_service.go @@ -23,6 +23,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/apache/incubator-answer/internal/service/event_queue" "time" "github.com/apache/incubator-answer/internal/base/constant" @@ -65,6 +66,7 @@ type UserService struct { userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo userNotificationConfigService *user_notification_config.UserNotificationConfigService questionService *questioncommon.QuestionCommon + eventQueueService event_queue.EventQueueService } func NewUserService(userRepo usercommon.UserRepo, @@ -79,6 +81,7 @@ func NewUserService(userRepo usercommon.UserRepo, userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo, userNotificationConfigService *user_notification_config.UserNotificationConfigService, questionService *questioncommon.QuestionCommon, + eventQueueService event_queue.EventQueueService, ) *UserService { return &UserService{ userCommonService: userCommonService, @@ -93,6 +96,7 @@ func NewUserService(userRepo usercommon.UserRepo, userNotificationConfigRepo: userNotificationConfigRepo, userNotificationConfigService: userNotificationConfigService, questionService: questionService, + eventQueueService: eventQueueService, } } @@ -352,6 +356,10 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq cond := us.formatUserInfoForUpdateInfo(oldUserInfo, req, siteUsers) err = us.userRepo.UpdateInfo(ctx, cond) + if err != nil { + return nil, err + } + us.eventQueueService.Send(ctx, schema.NewEvent(constant.EventUserUpdate, req.UserID)) return nil, err } diff --git a/internal/service/content/vote_service.go b/internal/service/content/vote_service.go index bfb403c9..c83011a9 100644 --- a/internal/service/content/vote_service.go +++ b/internal/service/content/vote_service.go @@ -21,6 +21,8 @@ package content import ( "context" + "fmt" + "github.com/apache/incubator-answer/internal/service/event_queue" "strings" "github.com/apache/incubator-answer/internal/service/activity_common" @@ -62,6 +64,7 @@ type VoteService struct { commentCommonRepo comment_common.CommentCommonRepo objectService *object_info.ObjService activityRepo activity_common.ActivityRepo + eventQueueService event_queue.EventQueueService } func NewVoteService( @@ -71,6 +74,7 @@ func NewVoteService( answerRepo answercommon.AnswerRepo, commentCommonRepo comment_common.CommentCommonRepo, objectService *object_info.ObjService, + eventQueueService event_queue.EventQueueService, ) *VoteService { return &VoteService{ voteRepo: voteRepo, @@ -79,6 +83,7 @@ func NewVoteService( answerRepo: answerRepo, commentCommonRepo: commentCommonRepo, objectService: objectService, + eventQueueService: eventQueueService, } } @@ -112,6 +117,9 @@ func (vs *VoteService) VoteUp(ctx context.Context, req *schema.VoteReq) (resp *s return nil, err } err = vs.voteRepo.Vote(ctx, voteUpOperationInfo) + if err != nil { + return nil, err + } } if err != nil { return nil, err @@ -125,6 +133,7 @@ func (vs *VoteService) VoteUp(ctx context.Context, req *schema.VoteReq) (resp *s resp.Votes = resp.UpVotes - resp.DownVotes if !req.IsCancel { resp.VoteStatus = constant.ActVoteUp + vs.sendEvent(ctx, req, objectInfo, resp) } return resp, nil } @@ -173,6 +182,7 @@ func (vs *VoteService) VoteDown(ctx context.Context, req *schema.VoteReq) (resp resp.Votes = resp.UpVotes - resp.DownVotes if !req.IsCancel { resp.VoteStatus = constant.ActVoteDown + vs.sendEvent(ctx, req, objectInfo, resp) } return resp, nil } @@ -289,3 +299,24 @@ func (vs *VoteService) getActivities(ctx context.Context, op *schema.VoteOperati } return activities } + +func (vs *VoteService) sendEvent(ctx context.Context, + req *schema.VoteReq, objectInfo *schema.SimpleObjectInfo, resp *schema.VoteResp) { + var event *schema.EventMsg + switch objectInfo.ObjectType { + case constant.QuestionObjectType: + event = schema.NewEvent(constant.EventQuestionVote, req.UserID). + QID(objectInfo.QuestionID, objectInfo.ObjectCreatorUserID) + case constant.AnswerObjectType: + event = schema.NewEvent(constant.EventAnswerVote, req.UserID). + AID(objectInfo.AnswerID, objectInfo.ObjectCreatorUserID) + case constant.CommentObjectType: + event = schema.NewEvent(constant.EventCommentVote, req.UserID). + CID(objectInfo.CommentID, objectInfo.ObjectCreatorUserID) + default: + return + } + event.AddExtra("vote_up_amount", fmt.Sprintf("%d", resp.UpVotes)) + event.AddExtra("vote_down_amount", fmt.Sprintf("%d", resp.DownVotes)) + vs.eventQueueService.Send(ctx, event) +} diff --git a/internal/service/event_queue/event_queue.go b/internal/service/event_queue/event_queue.go new file mode 100644 index 00000000..b89a3ccc --- /dev/null +++ b/internal/service/event_queue/event_queue.go @@ -0,0 +1,69 @@ +/* + * 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 event_queue + +import ( + "context" + + "github.com/apache/incubator-answer/internal/schema" + "github.com/segmentfault/pacman/log" +) + +type EventQueueService interface { + Send(ctx context.Context, msg *schema.EventMsg) + RegisterHandler(handler func(ctx context.Context, msg *schema.EventMsg) error) +} + +type eventQueueService struct { + Queue chan *schema.EventMsg + Handler func(ctx context.Context, msg *schema.EventMsg) error +} + +func (ns *eventQueueService) Send(ctx context.Context, msg *schema.EventMsg) { + ns.Queue <- msg +} + +func (ns *eventQueueService) RegisterHandler( + handler func(ctx context.Context, msg *schema.EventMsg) error) { + ns.Handler = handler +} + +func (ns *eventQueueService) working() { + go func() { + for msg := range ns.Queue { + log.Debugf("received badge %+v", msg) + if ns.Handler == nil { + log.Warnf("no handler for badge") + continue + } + if err := ns.Handler(context.Background(), msg); err != nil { + log.Error(err) + } + } + }() +} + +// NewEventQueueService create a new badge queue service +func NewEventQueueService() EventQueueService { + ns := &eventQueueService{} + ns.Queue = make(chan *schema.EventMsg, 128) + ns.working() + return ns +} diff --git a/internal/service/meta/meta_service.go b/internal/service/meta/meta_service.go index 1026b173..778c6ca6 100644 --- a/internal/service/meta/meta_service.go +++ b/internal/service/meta/meta_service.go @@ -23,6 +23,7 @@ import ( "context" "encoding/json" "errors" + "github.com/apache/incubator-answer/internal/service/event_queue" "strconv" "strings" @@ -46,14 +47,22 @@ type MetaService struct { userCommon *usercommon.UserCommon questionRepo questioncommon.QuestionRepo answerRepo answercommon.AnswerRepo + eventQueueService event_queue.EventQueueService } -func NewMetaService(metaCommonService *metacommon.MetaCommonService, userCommon *usercommon.UserCommon, answerRepo answercommon.AnswerRepo, questionRepo questioncommon.QuestionRepo) *MetaService { +func NewMetaService( + metaCommonService *metacommon.MetaCommonService, + userCommon *usercommon.UserCommon, + answerRepo answercommon.AnswerRepo, + questionRepo questioncommon.QuestionRepo, + eventQueueService event_queue.EventQueueService, +) *MetaService { return &MetaService{ metaCommonService: metaCommonService, questionRepo: questionRepo, userCommon: userCommon, answerRepo: answerRepo, + eventQueueService: eventQueueService, } } @@ -86,22 +95,27 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda if err != nil { return nil, err } + var event *schema.EventMsg if objectType == constant.AnswerObjectType { - _, exist, err := ms.answerRepo.GetAnswer(ctx, req.ObjectID) + answerInfo, exist, err := ms.answerRepo.GetAnswer(ctx, req.ObjectID) if err != nil { return nil, err } if !exist { return nil, myErrors.BadRequest(reason.AnswerNotFound) } + event = schema.NewEvent(constant.EventAnswerReact, req.UserID). + AID(answerInfo.ID, answerInfo.UserID) } else if objectType == constant.QuestionObjectType { - _, exist, err := ms.questionRepo.GetQuestion(ctx, req.ObjectID) + questionInfo, exist, err := ms.questionRepo.GetQuestion(ctx, req.ObjectID) if err != nil { return nil, err } if !exist { return nil, myErrors.BadRequest(reason.QuestionNotFound) } + event = schema.NewEvent(constant.EventQuestionReact, req.UserID). + QID(questionInfo.ID, questionInfo.UserID) } else { return nil, myErrors.BadRequest(reason.ObjectNotFound) } @@ -138,7 +152,7 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda if err != nil { return nil, err } - + ms.eventQueueService.Send(ctx, event) return resp, nil } diff --git a/internal/service/provider.go b/internal/service/provider.go index e82d9316..a22a6ea9 100644 --- a/internal/service/provider.go +++ b/internal/service/provider.go @@ -33,6 +33,7 @@ import ( "github.com/apache/incubator-answer/internal/service/config" "github.com/apache/incubator-answer/internal/service/content" "github.com/apache/incubator-answer/internal/service/dashboard" + "github.com/apache/incubator-answer/internal/service/event_queue" "github.com/apache/incubator-answer/internal/service/export" "github.com/apache/incubator-answer/internal/service/follow" "github.com/apache/incubator-answer/internal/service/meta" @@ -117,4 +118,5 @@ var ProviderSetService = wire.NewSet( notice_queue.NewNewQuestionNotificationQueueService, review.NewReviewService, meta.NewMetaService, + event_queue.NewEventQueueService, ) diff --git a/internal/service/report/report_service.go b/internal/service/report/report_service.go index 7f060a39..218423e1 100644 --- a/internal/service/report/report_service.go +++ b/internal/service/report/report_service.go @@ -21,6 +21,7 @@ package report import ( "encoding/json" + "github.com/apache/incubator-answer/internal/service/event_queue" "github.com/apache/incubator-answer/internal/base/constant" "github.com/apache/incubator-answer/internal/base/handler" @@ -55,6 +56,7 @@ type ReportService struct { commentCommonRepo comment_common.CommentCommonRepo reportHandle *report_handle.ReportHandle configService *config.ConfigService + eventQueueService event_queue.EventQueueService } // NewReportService new report service @@ -67,6 +69,7 @@ func NewReportService( commentCommonRepo comment_common.CommentCommonRepo, reportHandle *report_handle.ReportHandle, configService *config.ConfigService, + eventQueueService event_queue.EventQueueService, ) *ReportService { return &ReportService{ reportRepo: reportRepo, @@ -77,6 +80,7 @@ func NewReportService( commentCommonRepo: commentCommonRepo, reportHandle: reportHandle, configService: configService, + eventQueueService: eventQueueService, } } @@ -112,7 +116,12 @@ func (rs *ReportService) AddReport(ctx context.Context, req *schema.AddReportReq Content: req.Content, Status: entity.ReportStatusPending, } - return rs.reportRepo.AddReport(ctx, report) + err = rs.reportRepo.AddReport(ctx, report) + if err != nil { + return err + } + rs.sendEvent(ctx, report, objInfo) + return nil } // GetUnreviewedReportPostPage get unreviewed report post page @@ -218,3 +227,22 @@ func (rs *ReportService) ReviewReport(ctx context.Context, req *schema.ReviewRep return rs.reportRepo.UpdateStatus(ctx, report.ID, entity.ReportStatusCompleted) } + +func (rs *ReportService) sendEvent(ctx context.Context, + report *entity.Report, objectInfo *schema.SimpleObjectInfo) { + var event *schema.EventMsg + switch objectInfo.ObjectType { + case constant.QuestionObjectType: + event = schema.NewEvent(constant.EventQuestionFlag, report.UserID). + QID(objectInfo.QuestionID, objectInfo.ObjectCreatorUserID) + case constant.AnswerObjectType: + event = schema.NewEvent(constant.EventAnswerFlag, report.UserID). + AID(objectInfo.AnswerID, objectInfo.ObjectCreatorUserID) + case constant.CommentObjectType: + event = schema.NewEvent(constant.EventCommentFlag, report.UserID). + CID(objectInfo.CommentID, objectInfo.ObjectCreatorUserID) + default: + return + } + rs.eventQueueService.Send(ctx, event) +}
