This is an automated email from the ASF dual-hosted git repository.

linkinstar pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-answer.git

commit d9cca1bad10649ab628f7e11008d02a233d40522
Author: hgaol <[email protected]>
AuthorDate: Thu May 9 11:28:52 2024 +0800

    refactor meta_service and meta_common_service and fix other issues
---
 cmd/wire_gen.go                                    |  34 +---
 internal/repo/meta/meta_repo.go                    |  91 ++--------
 internal/schema/meta_schema.go                     |  14 +-
 internal/service/activity/activity.go              |   6 +-
 internal/service/content/question_service.go       |   6 +-
 internal/service/meta/meta_service.go              | 190 +++++++++++++--------
 .../service/meta_common/meta_common_service.go     |  99 +++++++++++
 internal/service/mock/siteinfo_repo_mock.go        |  19 ---
 internal/service/provider.go                       |   4 +-
 internal/service/question_common/question.go       |  12 +-
 ui/src/common/interface.ts                         |   7 +
 .../Detail/components/Reactions/index.tsx          |   6 +-
 ui/src/services/common.ts                          |   2 +-
 13 files changed, 272 insertions(+), 218 deletions(-)

diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index 4ce4612f..d17655c5 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -1,28 +1,8 @@
-//go:build !wireinject
-// +build !wireinject
-
-/*
- * 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.
- */
-
 // Code generated by Wire. DO NOT EDIT.
 
 //go:generate go run github.com/google/wire/cmd/wire
+//go:build !wireinject
+// +build !wireinject
 
 package answercmd
 
@@ -81,6 +61,7 @@ import (
        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"
+       "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/notification_common"
@@ -169,8 +150,8 @@ func initApplication(debug bool, serverConf *conf.Server, 
dbConf *data.Database,
        collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
        answerCommon := answercommon.NewAnswerCommon(answerRepo)
        metaRepo := meta.NewMetaRepo(dataData)
-       metaService := meta2.NewMetaService(metaRepo, userCommon)
-       questionCommon := questioncommon.NewQuestionCommon(questionRepo, 
answerRepo, voteRepo, followRepo, tagCommonService, userCommon, 
collectionCommon, answerCommon, metaService, configService, 
activityQueueService, revisionRepo, 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)
        captchaRepo := captcha.NewCaptchaRepo(dataData)
        captchaService := action.NewCaptchaService(captchaRepo)
@@ -193,7 +174,7 @@ 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, notificationQueueService, 
siteInfoCommonService)
-       questionService := content.NewQuestionService(questionRepo, 
tagCommonService, questionCommon, userCommon, userRepo, userRoleRelService, 
revisionService, metaService, collectionCommon, answerActivityService, 
emailService, notificationQueueService, externalNotificationQueueService, 
activityQueueService, siteInfoCommonService, externalNotificationService, 
reviewService, configService)
+       questionService := content.NewQuestionService(questionRepo, 
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)
        reportHandle := report_handle.NewReportHandle(questionService, 
answerService, commentService)
        reportService := report2.NewReportService(reportRepo, objService, 
userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, 
configService)
@@ -240,7 +221,7 @@ func initApplication(debug bool, serverConf *conf.Server, 
dbConf *data.Database,
        activityActivityRepo := activity.NewActivityRepo(dataData, 
configService)
        activityCommon := activity_common2.NewActivityCommon(activityRepo, 
activityQueueService)
        commentCommonService := 
comment_common.NewCommentCommonService(commentCommonRepo)
-       activityService := activity2.NewActivityService(activityActivityRepo, 
userCommon, activityCommon, tagCommonService, objService, commentCommonService, 
revisionService, metaService, configService)
+       activityService := activity2.NewActivityService(activityActivityRepo, 
userCommon, activityCommon, tagCommonService, objService, commentCommonService, 
revisionService, metaCommonService, configService)
        activityController := controller.NewActivityController(activityService)
        roleController := controller_admin.NewRoleController(roleService)
        pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
@@ -250,6 +231,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)
        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)
diff --git a/internal/repo/meta/meta_repo.go b/internal/repo/meta/meta_repo.go
index c3e85c71..384fd185 100644
--- a/internal/repo/meta/meta_repo.go
+++ b/internal/repo/meta/meta_repo.go
@@ -21,13 +21,11 @@ package meta
 
 import (
        "context"
-       "encoding/json"
 
        "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/schema"
-       "github.com/apache/incubator-answer/internal/service/meta"
+       "github.com/apache/incubator-answer/internal/service/meta_common"
        "github.com/segmentfault/pacman/errors"
        "xorm.io/builder"
        "xorm.io/xorm"
@@ -39,7 +37,7 @@ type metaRepo struct {
 }
 
 // NewMetaRepo new repository
-func NewMetaRepo(data *data.Data) meta.MetaRepo {
+func NewMetaRepo(data *data.Data) metacommon.MetaRepo {
        return &metaRepo{
                data: data,
        }
@@ -73,97 +71,32 @@ func (mr *metaRepo) UpdateMeta(ctx context.Context, meta 
*entity.Meta) (err erro
 }
 
 // AddOrUpdateMetaByObjectIdAndKey if exist record with same objectID and key, 
update it. Or create a new one
-func (mr *metaRepo) AddOrUpdateMetaByObjectIdAndKey(ctx context.Context, req 
*schema.UpdateReactionReq) (schema.ReactSummaryMeta, error) {
-       result, err := mr.data.DB.Transaction(func(session *xorm.Session) 
(interface{}, error) {
+func (mr *metaRepo) AddOrUpdateMetaByObjectIdAndKey(ctx context.Context, 
objectId, key string, f func(*entity.Meta, bool) (*entity.Meta, error)) error {
+       _, err := mr.data.DB.Transaction(func(session *xorm.Session) 
(interface{}, error) {
                session = session.Context(ctx)
 
                // 1. acquire meta entity with target object id and key
                metaEntity := &entity.Meta{}
-               exist, err := 
mr.data.DB.Context(ctx).Where(builder.Eq{"object_id": 
req.ObjectID}.And(builder.Eq{"`key`": 
entity.ObjectReactSummaryKey})).ForUpdate().Get(metaEntity)
+               exist, err := session.Where(builder.Eq{"object_id": 
objectId}.And(builder.Eq{"`key`": key})).ForUpdate().Get(metaEntity)
                if err != nil {
-                       return nil, 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
-               }
-
-               var reaction schema.ReactSummaryMeta
-               // if not exist, create new one
-               if !exist {
-                       reaction = schema.ReactSummaryMeta{}
-               } else {
-                       err = json.Unmarshal([]byte(metaEntity.Value), 
&reaction)
-                       if err != nil {
-                               return nil, err
-                       }
+                       return nil, err
                }
 
-               // update reaction
-               mr.updateReaction(req, reaction)
-
-               // write back to meta repo
-               reactSumBytes, err := json.Marshal(reaction)
+               meta, err := f(metaEntity, exist)
                if err != nil {
                        return nil, err
                }
 
-               metaObj := &entity.Meta{
-                       ObjectID: req.ObjectID,
-                       Key:      entity.ObjectReactSummaryKey,
-                       Value:    string(reactSumBytes),
-               }
+               // return entity.Meta
                if exist {
-                       _, err = session.Update(metaObj)
+                       _, err = session.ID(metaEntity.ID).Update(meta)
                } else {
-                       _, err = session.Insert(metaObj)
+                       _, err = session.Insert(meta)
                }
 
-               return reaction, err
+               return nil, err
        })
-
-       if err != nil {
-               return nil, 
errors.InternalServer(reason.DatabaseError).WithError(err)
-       }
-
-       if ret, ok := result.(schema.ReactSummaryMeta); ok {
-               return ret, nil
-       } else {
-               return nil, 
errors.InternalServer(reason.UnknownError).WithMsg("Unable to cast to 
schema.ReactSummaryMeta.")
-       }
-}
-
-// updateReaction update reaction
-func (mr *metaRepo) updateReaction(req *schema.UpdateReactionReq, reaction 
schema.ReactSummaryMeta) {
-       emojiUserIds, ok := reaction[req.Emoji]
-
-       if !ok {
-               emojiUserIds = make([]string, 0)
-       }
-
-       found := false
-       for _, item := range emojiUserIds {
-               if item == req.UserID {
-                       found = true
-                       break
-               }
-       }
-
-       removeItem := func(arr []string, target string) []string {
-               result := make([]string, 0, len(arr))
-
-               for _, item := range arr {
-                       if item != target {
-                               result = append(result, item)
-                       }
-               }
-
-               return result
-       }
-
-       if req.Reaction == "activate" && !found {
-               emojiUserIds = append(emojiUserIds, req.UserID)
-       } else if req.Reaction == "deactivate" && found {
-               emojiUserIds = removeItem(emojiUserIds, req.UserID)
-       }
-
-       reaction[req.Emoji] = emojiUserIds
+       return err
 }
 
 // GetMetaByObjectIdAndKey get meta one
diff --git a/internal/schema/meta_schema.go b/internal/schema/meta_schema.go
index bf458bfc..0774aca2 100644
--- a/internal/schema/meta_schema.go
+++ b/internal/schema/meta_schema.go
@@ -20,16 +20,20 @@
 package schema
 
 type UpdateReactionReq struct {
-       ObjectID string `validate:"required" form:"object_id" json:"object_id"` 
// object id
-       Emoji    string `validate:"required" form:"emoji" json:"emoji"`         
// emoji
-       Reaction string `validate:"required" form:"reaction" json:"reaction"`   
// reaction
+       ObjectID string `validate:"required" json:"object_id"`
+       Emoji    string `validate:"required,oneof=heart smile frown" 
json:"emoji"`
+       Reaction string `validate:"required,oneof=activate deactivate" 
json:"reaction"`
        UserID   string `json:"-"`
 }
 
 type GetReactionReq struct {
-       ObjectID string `validate:"required" form:"object_id" json:"object_id"` 
// object id
+       ObjectID string `validate:"required" form:"object_id"`
 }
 
 type ReactSummaryMeta map[string][]string
 
-type ReactionResp ReactSummaryMeta
+type ReactionResp struct {
+       // reaction summary is a map, key is emoji, value is username list
+       // such as {"heart": ["jack", "tom"], "smile": ["andy"], "frown": 
["bob"]}
+       ReactionSummary ReactSummaryMeta `json:"reaction_summary"`
+}
diff --git a/internal/service/activity/activity.go 
b/internal/service/activity/activity.go
index f6d692f5..7188fa94 100644
--- a/internal/service/activity/activity.go
+++ b/internal/service/activity/activity.go
@@ -26,6 +26,7 @@ import (
        "strings"
 
        "github.com/apache/incubator-answer/internal/service/activity_common"
+       "github.com/apache/incubator-answer/internal/service/meta_common"
 
        "github.com/apache/incubator-answer/internal/base/constant"
        "github.com/apache/incubator-answer/internal/base/handler"
@@ -33,7 +34,6 @@ import (
        "github.com/apache/incubator-answer/internal/schema"
        "github.com/apache/incubator-answer/internal/service/comment_common"
        "github.com/apache/incubator-answer/internal/service/config"
-       "github.com/apache/incubator-answer/internal/service/meta"
        "github.com/apache/incubator-answer/internal/service/object_info"
        "github.com/apache/incubator-answer/internal/service/revision_common"
        "github.com/apache/incubator-answer/internal/service/tag_common"
@@ -58,7 +58,7 @@ type ActivityService struct {
        objectInfoService     *object_info.ObjService
        commentCommonService  *comment_common.CommentCommonService
        revisionService       *revision_common.RevisionService
-       metaService           *meta.MetaService
+       metaService           *metacommon.MetaCommonService
        configService         *config.ConfigService
 }
 
@@ -71,7 +71,7 @@ func NewActivityService(
        objectInfoService *object_info.ObjService,
        commentCommonService *comment_common.CommentCommonService,
        revisionService *revision_common.RevisionService,
-       metaService *meta.MetaService,
+       metaService *metacommon.MetaCommonService,
        configService *config.ConfigService,
 ) *ActivityService {
        return &ActivityService{
diff --git a/internal/service/content/question_service.go 
b/internal/service/content/question_service.go
index 171f67bd..e11a608b 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -38,7 +38,7 @@ import (
        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"
+       "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"
@@ -71,7 +71,7 @@ type QuestionService struct {
        userRepo                         usercommon.UserRepo
        userRoleRelService               *role.UserRoleRelService
        revisionService                  *revision_common.RevisionService
-       metaService                      *meta.MetaService
+       metaService                      *metacommon.MetaCommonService
        collectionCommon                 *collectioncommon.CollectionCommon
        answerActivityService            *activity.AnswerActivityService
        emailService                     *export.EmailService
@@ -92,7 +92,7 @@ func NewQuestionService(
        userRepo usercommon.UserRepo,
        userRoleRelService *role.UserRoleRelService,
        revisionService *revision_common.RevisionService,
-       metaService *meta.MetaService,
+       metaService *metacommon.MetaCommonService,
        collectionCommon *collectioncommon.CollectionCommon,
        answerActivityService *activity.AnswerActivityService,
        emailService *export.EmailService,
diff --git a/internal/service/meta/meta_service.go 
b/internal/service/meta/meta_service.go
index b06fa736..a22e0237 100644
--- a/internal/service/meta/meta_service.go
+++ b/internal/service/meta/meta_service.go
@@ -24,110 +24,117 @@ import (
        "encoding/json"
        "errors"
 
+       "github.com/apache/incubator-answer/internal/base/constant"
        "github.com/apache/incubator-answer/internal/base/reason"
        "github.com/apache/incubator-answer/internal/entity"
        "github.com/apache/incubator-answer/internal/schema"
+       answercommon 
"github.com/apache/incubator-answer/internal/service/answer_common"
+       metacommon 
"github.com/apache/incubator-answer/internal/service/meta_common"
+       questioncommon 
"github.com/apache/incubator-answer/internal/service/question_common"
        usercommon 
"github.com/apache/incubator-answer/internal/service/user_common"
+       "github.com/apache/incubator-answer/pkg/obj"
        myErrors "github.com/segmentfault/pacman/errors"
 )
 
-// MetaRepo meta repository
-type MetaRepo interface {
-       AddMeta(ctx context.Context, meta *entity.Meta) (err error)
-       RemoveMeta(ctx context.Context, id int) (err error)
-       UpdateMeta(ctx context.Context, meta *entity.Meta) (err error)
-       AddOrUpdateMetaByObjectIdAndKey(ctx context.Context, req 
*schema.UpdateReactionReq) (schema.ReactSummaryMeta, error)
-       GetMetaByObjectIdAndKey(ctx context.Context, objectId, key string) 
(meta *entity.Meta, exist bool, err error)
-       GetMetaList(ctx context.Context, meta *entity.Meta) (metas 
[]*entity.Meta, err error)
-}
-
 // MetaService user service
 type MetaService struct {
-       metaRepo   MetaRepo
-       userCommon *usercommon.UserCommon
+       metaCommonService *metacommon.MetaCommonService
+       userCommon        *usercommon.UserCommon
+       questionRepo      questioncommon.QuestionRepo
+       answerRepo        answercommon.AnswerRepo
 }
 
-func NewMetaService(metaRepo MetaRepo, userCommon *usercommon.UserCommon) 
*MetaService {
+func NewMetaService(metaCommonService *metacommon.MetaCommonService, 
userCommon *usercommon.UserCommon, answerRepo answercommon.AnswerRepo, 
questionRepo questioncommon.QuestionRepo) *MetaService {
        return &MetaService{
-               metaRepo:   metaRepo,
-               userCommon: userCommon,
-       }
-}
-
-// AddMeta add meta
-func (ms *MetaService) AddMeta(ctx context.Context, objID, key, value string) 
(err error) {
-       meta := &entity.Meta{
-               ObjectID: objID,
-               Key:      key,
-               Value:    value,
-       }
-       return ms.metaRepo.AddMeta(ctx, meta)
-}
-
-// RemoveMeta delete meta
-func (ms *MetaService) RemoveMeta(ctx context.Context, id int) (err error) {
-       return ms.metaRepo.RemoveMeta(ctx, id)
-}
-
-// UpdateMeta update meta
-func (ms *MetaService) UpdateMeta(ctx context.Context, metaID int, key, value 
string) (err error) {
-       meta := &entity.Meta{
-               ID:    metaID,
-               Key:   key,
-               Value: value,
-       }
-       return ms.metaRepo.UpdateMeta(ctx, meta)
-}
-
-// GetMetaByObjectIdAndKey get meta one
-func (ms *MetaService) GetMetaByObjectIdAndKey(ctx context.Context, objectID, 
key string) (meta *entity.Meta, err error) {
-       meta, exist, err := ms.metaRepo.GetMetaByObjectIdAndKey(ctx, objectID, 
key)
-       if err != nil {
-               return
+               metaCommonService: metaCommonService,
+               questionRepo:      questionRepo,
+               userCommon:        userCommon,
+               answerRepo:        answerRepo,
        }
-       if !exist {
-               return nil, myErrors.BadRequest(reason.MetaObjectNotFound)
-       }
-       return meta, nil
-}
-
-// GetMetaList get meta list all
-func (ms *MetaService) GetMetaList(ctx context.Context, objID string) (metas 
[]*entity.Meta, err error) {
-       metas, err = ms.metaRepo.GetMetaList(ctx, &entity.Meta{ObjectID: objID})
-       if err != nil {
-               return nil, err
-       }
-       return metas, err
 }
 
 // GetReactionByObjectId get reaction
-func (ms *MetaService) GetReactionByObjectId(ctx context.Context, objectID 
string) (resp schema.ReactionResp, err error) {
-       reactionMeta, err := ms.GetMetaByObjectIdAndKey(ctx, objectID, 
entity.ObjectReactSummaryKey)
+func (ms *MetaService) GetReactionByObjectId(ctx context.Context, objectID 
string) (resp *schema.ReactionResp, err error) {
+       resp = &schema.ReactionResp{}
+       reactionMeta, err := ms.metaCommonService.GetMetaByObjectIdAndKey(ctx, 
objectID, entity.ObjectReactSummaryKey)
 
        // if not exist, return nil
        if err != nil {
                var pacmanErr *myErrors.Error
                if errors.As(err, &pacmanErr) && pacmanErr.Reason == 
reason.MetaObjectNotFound {
-                       return nil, nil
+                       return resp, nil
                } else {
-                       return nil, err
+                       return resp, err
                }
        }
 
        var reaction schema.ReactSummaryMeta
        err = json.Unmarshal([]byte(reactionMeta.Value), &reaction)
        if err != nil {
-               return nil, err
+               return resp, err
        }
        return ms.convertToReactionResp(ctx, reaction)
 }
 
 // AddOrUpdateReaction add or update reaction
-func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req 
*schema.UpdateReactionReq) (resp schema.ReactionResp, err error) {
-       reaction, err := ms.metaRepo.AddOrUpdateMetaByObjectIdAndKey(ctx, req)
+func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req 
*schema.UpdateReactionReq) (resp *schema.ReactionResp, err error) {
+       // check if object exist and it's answer or question
+       objectType, err := obj.GetObjectTypeStrByObjectID(req.ObjectID)
        if err != nil {
                return nil, err
        }
+       if objectType == constant.AnswerObjectType {
+               _, exist, err := ms.answerRepo.GetAnswer(ctx, req.ObjectID)
+               if err != nil {
+                       return nil, err
+               }
+               if !exist {
+                       return nil, myErrors.BadRequest(reason.AnswerNotFound)
+               }
+       } else if objectType == constant.QuestionObjectType {
+               _, exist, err := ms.questionRepo.GetQuestion(ctx, req.ObjectID)
+               if err != nil {
+                       return nil, err
+               }
+               if !exist {
+                       return nil, myErrors.BadRequest(reason.QuestionNotFound)
+               }
+       } else {
+               return nil, myErrors.BadRequest(reason.ObjectNotFound)
+       }
+
+       // add or update
+       var reaction schema.ReactSummaryMeta
+       err = ms.metaCommonService.AddOrUpdateMetaByObjectIdAndKey(ctx, 
req.ObjectID, entity.ObjectReactSummaryKey, func(meta *entity.Meta, exist bool) 
(*entity.Meta, error) {
+               // if not exist, create new one
+               if !exist {
+                       reaction = schema.ReactSummaryMeta{}
+               } else {
+                       err = json.Unmarshal([]byte(meta.Value), &reaction)
+                       if err != nil {
+                               return nil, err
+                       }
+               }
+
+               // update reaction
+               ms.updateReaction(req, reaction)
+
+               // write back to meta repo
+               reactSumBytes, err := json.Marshal(reaction)
+               if err != nil {
+                       return nil, err
+               }
+
+               return &entity.Meta{
+                       ObjectID: req.ObjectID,
+                       Key:      entity.ObjectReactSummaryKey,
+                       Value:    string(reactSumBytes),
+               }, nil
+       })
+
+       if err != nil {
+               return nil, 
myErrors.InternalServer(reason.DatabaseError).WithError(err)
+       }
 
        resp, err = ms.convertToReactionResp(ctx, reaction)
        if err != nil {
@@ -137,20 +144,59 @@ func (ms *MetaService) AddOrUpdateReaction(ctx 
context.Context, req *schema.Upda
        return resp, nil
 }
 
-func (ms *MetaService) convertToReactionResp(ctx context.Context, reaction 
schema.ReactSummaryMeta) (schema.ReactionResp, error) {
-       resp := schema.ReactionResp{}
+// updateReaction update reaction
+func (ms *MetaService) updateReaction(req *schema.UpdateReactionReq, reaction 
schema.ReactSummaryMeta) {
+       emojiUserIds, ok := reaction[req.Emoji]
+
+       if !ok {
+               emojiUserIds = make([]string, 0)
+       }
+
+       found := false
+       for _, item := range emojiUserIds {
+               if item == req.UserID {
+                       found = true
+                       break
+               }
+       }
+
+       removeItem := func(arr []string, target string) []string {
+               result := make([]string, 0, len(arr))
+
+               for _, item := range arr {
+                       if item != target {
+                               result = append(result, item)
+                       }
+               }
+
+               return result
+       }
+
+       if req.Reaction == "activate" && !found {
+               emojiUserIds = append(emojiUserIds, req.UserID)
+       } else if req.Reaction == "deactivate" && found {
+               emojiUserIds = removeItem(emojiUserIds, req.UserID)
+       }
+
+       reaction[req.Emoji] = emojiUserIds
+}
+
+func (ms *MetaService) convertToReactionResp(ctx context.Context, reaction 
schema.ReactSummaryMeta) (*schema.ReactionResp, error) {
+       resp := &schema.ReactionResp{
+               ReactionSummary: make(schema.ReactSummaryMeta),
+       }
        // traverse map and convert to username
        for emoji, userIds := range reaction {
                userNames := make([]string, 0)
                userBasicInfos, err := 
ms.userCommon.BatchUserBasicInfoByID(ctx, userIds)
                if err != nil {
-                       return nil, err
+                       return resp, err
                }
                // get username
                for _, userBasicInfo := range userBasicInfos {
                        userNames = append(userNames, userBasicInfo.Username)
                }
-               resp[emoji] = userNames
+               resp.ReactionSummary[emoji] = userNames
        }
 
        return resp, nil
diff --git a/internal/service/meta_common/meta_common_service.go 
b/internal/service/meta_common/meta_common_service.go
new file mode 100644
index 00000000..d34f7d43
--- /dev/null
+++ b/internal/service/meta_common/meta_common_service.go
@@ -0,0 +1,99 @@
+/*
+ * 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 metacommon
+
+import (
+       "context"
+
+       "github.com/apache/incubator-answer/internal/base/reason"
+       "github.com/apache/incubator-answer/internal/entity"
+       myErrors "github.com/segmentfault/pacman/errors"
+)
+
+// MetaRepo meta repository
+type MetaRepo interface {
+       AddMeta(ctx context.Context, meta *entity.Meta) (err error)
+       RemoveMeta(ctx context.Context, id int) (err error)
+       UpdateMeta(ctx context.Context, meta *entity.Meta) (err error)
+       AddOrUpdateMetaByObjectIdAndKey(ctx context.Context, objectId, key 
string, f func(*entity.Meta, bool) (*entity.Meta, error)) error
+       GetMetaByObjectIdAndKey(ctx context.Context, objectId, key string) 
(meta *entity.Meta, exist bool, err error)
+       GetMetaList(ctx context.Context, meta *entity.Meta) (metas 
[]*entity.Meta, err error)
+}
+
+// MetaCommonService user service
+type MetaCommonService struct {
+       metaRepo MetaRepo
+}
+
+func NewMetaCommonService(metaRepo MetaRepo) *MetaCommonService {
+       return &MetaCommonService{
+               metaRepo: metaRepo,
+       }
+}
+
+// AddMeta add meta
+func (ms *MetaCommonService) AddMeta(ctx context.Context, objID, key, value 
string) (err error) {
+       meta := &entity.Meta{
+               ObjectID: objID,
+               Key:      key,
+               Value:    value,
+       }
+       return ms.metaRepo.AddMeta(ctx, meta)
+}
+
+// RemoveMeta delete meta
+func (ms *MetaCommonService) RemoveMeta(ctx context.Context, id int) (err 
error) {
+       return ms.metaRepo.RemoveMeta(ctx, id)
+}
+
+// UpdateMeta update meta
+func (ms *MetaCommonService) UpdateMeta(ctx context.Context, metaID int, key, 
value string) (err error) {
+       meta := &entity.Meta{
+               ID:    metaID,
+               Key:   key,
+               Value: value,
+       }
+       return ms.metaRepo.UpdateMeta(ctx, meta)
+}
+
+func (ms *MetaCommonService) AddOrUpdateMetaByObjectIdAndKey(ctx 
context.Context, objID, key string, f func(*entity.Meta, bool) (*entity.Meta, 
error)) (err error) {
+       return ms.metaRepo.AddOrUpdateMetaByObjectIdAndKey(ctx, objID, key, f)
+}
+
+// GetMetaByObjectIdAndKey get meta one
+func (ms *MetaCommonService) GetMetaByObjectIdAndKey(ctx context.Context, 
objectID, key string) (meta *entity.Meta, err error) {
+       meta, exist, err := ms.metaRepo.GetMetaByObjectIdAndKey(ctx, objectID, 
key)
+       if err != nil {
+               return
+       }
+       if !exist {
+               return nil, myErrors.BadRequest(reason.MetaObjectNotFound)
+       }
+       return meta, nil
+}
+
+// GetMetaList get meta list all
+func (ms *MetaCommonService) GetMetaList(ctx context.Context, objID string) 
(metas []*entity.Meta, err error) {
+       metas, err = ms.metaRepo.GetMetaList(ctx, &entity.Meta{ObjectID: objID})
+       if err != nil {
+               return nil, err
+       }
+       return metas, err
+}
diff --git a/internal/service/mock/siteinfo_repo_mock.go 
b/internal/service/mock/siteinfo_repo_mock.go
index abc1fe07..cdc72b80 100644
--- a/internal/service/mock/siteinfo_repo_mock.go
+++ b/internal/service/mock/siteinfo_repo_mock.go
@@ -1,22 +1,3 @@
-/*
- * 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.
- */
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: ./siteinfo_service.go
 
diff --git a/internal/service/provider.go b/internal/service/provider.go
index 2bdd42b6..e82d9316 100644
--- a/internal/service/provider.go
+++ b/internal/service/provider.go
@@ -36,6 +36,7 @@ import (
        "github.com/apache/incubator-answer/internal/service/export"
        "github.com/apache/incubator-answer/internal/service/follow"
        "github.com/apache/incubator-answer/internal/service/meta"
+       "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"
        notficationcommon 
"github.com/apache/incubator-answer/internal/service/notification_common"
@@ -89,7 +90,7 @@ var ProviderSetService = wire.NewSet(
        rank.NewRankService,
        search_parser.NewSearchParser,
        content.NewSearchService,
-       meta.NewMetaService,
+       metacommon.NewMetaCommonService,
        object_info.NewObjService,
        report_handle.NewReportHandle,
        user_admin.NewUserAdminService,
@@ -115,4 +116,5 @@ var ProviderSetService = wire.NewSet(
        notification.NewExternalNotificationService,
        notice_queue.NewNewQuestionNotificationQueueService,
        review.NewReviewService,
+       meta.NewMetaService,
 )
diff --git a/internal/service/question_common/question.go 
b/internal/service/question_common/question.go
index ff0b9da5..fce3761e 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -32,7 +32,7 @@ import (
        "github.com/apache/incubator-answer/internal/service/activity_common"
        "github.com/apache/incubator-answer/internal/service/activity_queue"
        "github.com/apache/incubator-answer/internal/service/config"
-       "github.com/apache/incubator-answer/internal/service/meta"
+       metacommon 
"github.com/apache/incubator-answer/internal/service/meta_common"
        "github.com/apache/incubator-answer/internal/service/revision"
        "github.com/apache/incubator-answer/pkg/checker"
        "github.com/apache/incubator-answer/pkg/htmltext"
@@ -86,7 +86,7 @@ type QuestionCommon struct {
        userCommon           *usercommon.UserCommon
        collectionCommon     *collectioncommon.CollectionCommon
        AnswerCommon         *answercommon.AnswerCommon
-       metaService          *meta.MetaService
+       metaCommonService    *metacommon.MetaCommonService
        configService        *config.ConfigService
        activityQueueService activity_queue.ActivityQueueService
        revisionRepo         revision.RevisionRepo
@@ -101,7 +101,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
        userCommon *usercommon.UserCommon,
        collectionCommon *collectioncommon.CollectionCommon,
        answerCommon *answercommon.AnswerCommon,
-       metaService *meta.MetaService,
+       metaCommonService *metacommon.MetaCommonService,
        configService *config.ConfigService,
        activityQueueService activity_queue.ActivityQueueService,
        revisionRepo revision.RevisionRepo,
@@ -116,7 +116,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
                userCommon:           userCommon,
                collectionCommon:     collectionCommon,
                AnswerCommon:         answerCommon,
-               metaService:          metaService,
+               metaCommonService:    metaCommonService,
                configService:        configService,
                activityQueueService: activityQueueService,
                revisionRepo:         revisionRepo,
@@ -235,7 +235,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, 
questionID string, loginUser
        }
        resp = qs.ShowFormat(ctx, questionInfo)
        if resp.Status == entity.QuestionStatusClosed {
-               metaInfo, err := qs.metaService.GetMetaByObjectIdAndKey(ctx, 
questionInfo.ID, entity.QuestionCloseReasonKey)
+               metaInfo, err := 
qs.metaCommonService.GetMetaByObjectIdAndKey(ctx, questionInfo.ID, 
entity.QuestionCloseReasonKey)
                if err != nil {
                        log.Error(err)
                } else {
@@ -528,7 +528,7 @@ func (qs *QuestionCommon) CloseQuestion(ctx 
context.Context, req *schema.CloseQu
                CloseType: req.CloseType,
                CloseMsg:  req.CloseMsg,
        })
-       err = qs.metaService.AddMeta(ctx, req.ID, 
entity.QuestionCloseReasonKey, string(closeMeta))
+       err = qs.metaCommonService.AddMeta(ctx, req.ID, 
entity.QuestionCloseReasonKey, string(closeMeta))
        if err != nil {
                return err
        }
diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts
index 53f8eef5..d5f64f40 100644
--- a/ui/src/common/interface.ts
+++ b/ui/src/common/interface.ts
@@ -719,3 +719,10 @@ export interface PutFlagReviewParams {
   captcha_code?: any;
   captcha_id?: any;
 }
+
+/**
+ * @description response for reaction
+ */
+export interface ReactionItems {
+  reaction_summary: Record<string, string[]>;
+}
diff --git a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx 
b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx
index 5ade0319..0d380204 100644
--- a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx
+++ b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx
@@ -36,13 +36,13 @@ const Index: FC<Props> = ({
   showAddCommentBtn,
   handleClickComment,
 }) => {
-  const [reactions, setReactions] = useState<Record<string, string[]>>({});
+  const [reactions, setReactions] = useState<Record<string, string[]>>();
   const { t } = useTranslation('translation');
   const { username = '' } = loggedUserInfoStore((state) => state.user);
 
   useEffect(() => {
     queryReactions(objectId).then((res) => {
-      setReactions(res);
+      setReactions(res.reaction_summary);
     });
   }, []);
 
@@ -59,7 +59,7 @@ const Index: FC<Props> = ({
       reaction = 'deactivate';
     }
     updateReaction({ ...params, reaction }).then((res) => {
-      setReactions(res);
+      setReactions(res.reaction_summary);
     });
   };
 
diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts
index a783fa49..dff52cb7 100644
--- a/ui/src/services/common.ts
+++ b/ui/src/services/common.ts
@@ -93,7 +93,7 @@ export const updateReaction = (params) => {
 };
 
 export const queryReactions = (object_id: string) => {
-  return request.get<Record<string, string[]>>(
+  return request.get<Type.ReactionItems>(
     `/answer/api/v1/meta/reaction?object_id=${object_id}`,
   );
 };


Reply via email to