/* * 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 meta import ( "context" "encoding/json" "errors" "strconv" "strings" "github.com/apache/answer/internal/service/eventqueue" "github.com/apache/answer/internal/base/constant" "github.com/apache/answer/internal/base/handler" "github.com/apache/answer/internal/base/reason" "github.com/apache/answer/internal/base/translator" "github.com/apache/answer/internal/entity" "github.com/apache/answer/internal/schema" answercommon "github.com/apache/answer/internal/service/answer_common" metacommon "github.com/apache/answer/internal/service/meta_common" questioncommon "github.com/apache/answer/internal/service/question_common" usercommon "github.com/apache/answer/internal/service/user_common" "github.com/apache/answer/pkg/obj" myErrors "github.com/segmentfault/pacman/errors" ) // MetaService user service type MetaService struct { metaCommonService *metacommon.MetaCommonService userCommon *usercommon.UserCommon questionRepo questioncommon.QuestionRepo answerRepo answercommon.AnswerRepo eventQueueService eventqueue.Service } func NewMetaService( metaCommonService *metacommon.MetaCommonService, userCommon *usercommon.UserCommon, answerRepo answercommon.AnswerRepo, questionRepo questioncommon.QuestionRepo, eventQueueService eventqueue.Service, ) *MetaService { return &MetaService{ metaCommonService: metaCommonService, questionRepo: questionRepo, userCommon: userCommon, answerRepo: answerRepo, eventQueueService: eventQueueService, } } // GetReactionByObjectId get reaction func (ms *MetaService) GetReactionByObjectId(ctx context.Context, req *schema.GetReactionReq) (resp *schema.GetReactionByObjectIdResp, err error) { reactionMeta, err := ms.metaCommonService.GetMetaByObjectIdAndKey(ctx, req.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 } else { return nil, err } } var reaction schema.ReactionsSummaryMeta err = json.Unmarshal([]byte(reactionMeta.Value), &reaction) if err != nil { return nil, err } return ms.convertToReactionResp(ctx, req.UserID, &reaction) } // AddOrUpdateReaction add or update reaction func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.UpdateReactionReq) (resp *schema.GetReactionByObjectIdResp, err error) { // check if object exist and it's answer or question objectType, err := obj.GetObjectTypeStrByObjectID(req.ObjectID) if err != nil { return nil, err } var event *schema.EventMsg switch objectType { case constant.AnswerObjectType: 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).TID(answerInfo.ID). AID(answerInfo.ID, answerInfo.UserID) case constant.QuestionObjectType: 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).TID(questionInfo.ID). QID(questionInfo.ID, questionInfo.UserID) default: return nil, myErrors.BadRequest(reason.ObjectNotFound) } // add or update reactions := &schema.ReactionsSummaryMeta{} 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 { _ = json.Unmarshal([]byte(meta.Value), reactions) } // update reaction ms.updateReaction(req, reactions) // write back to meta repo reactSumBytes, err := json.Marshal(reactions) 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, req.UserID, reactions) if err != nil { return nil, err } ms.eventQueueService.Send(ctx, event) return resp, nil } // updateReaction update reaction func (ms *MetaService) updateReaction(req *schema.UpdateReactionReq, reactions *schema.ReactionsSummaryMeta) { switch req.Reaction { case "activate": reactions.AddReactionSummary(req.Emoji, req.UserID) case "deactivate": reactions.RemoveReactionSummary(req.Emoji, req.UserID) } } func (ms *MetaService) convertToReactionResp(ctx context.Context, userId string, r *schema.ReactionsSummaryMeta) ( resp *schema.GetReactionByObjectIdResp, err error) { lang := handler.GetLangByCtx(ctx) resp = &schema.GetReactionByObjectIdResp{ ReactionSummary: make([]*schema.ReactionRespItem, 0), } // traverse map and convert to username for _, reaction := range r.Reactions { item := &schema.ReactionRespItem{ Emoji: reaction.Emoji, IsActive: r.CheckUserInReactionSummary(reaction.Emoji, userId), } usernames := make([]string, 0) userBasicInfos, err := ms.userCommon.BatchUserBasicInfoByID(ctx, reaction.UserIDs) item.Count = len(userBasicInfos) if err != nil { return resp, err } // get username for _, userBasicInfo := range userBasicInfos { usernames = append(usernames, userBasicInfo.Username) if len(usernames) == 5 && len(userBasicInfos) > 5 { item.Tooltip = translator.TrWithData(lang, constant.ReactionTooltipLabel, map[string]string{ "Count": strconv.Itoa(len(userBasicInfos) - 5), "Names": strings.Join(usernames, ", "), }) break } } if len(userBasicInfos) <= 5 { item.Tooltip = strings.Join(usernames, ", ") } resp.ReactionSummary = append(resp.ReactionSummary, item) } return resp, nil }