chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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 notificationcommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/internal/base/translator"
|
||||
"github.com/apache/answer/internal/service/siteinfo_common"
|
||||
"github.com/apache/answer/internal/service/user_external_login"
|
||||
"github.com/apache/answer/pkg/display"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/noticequeue"
|
||||
"github.com/apache/answer/internal/service/object_info"
|
||||
usercommon "github.com/apache/answer/internal/service/user_common"
|
||||
"github.com/apache/answer/pkg/uid"
|
||||
"github.com/apache/answer/plugin"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type NotificationRepo interface {
|
||||
AddNotification(ctx context.Context, notification *entity.Notification) (err error)
|
||||
GetNotificationPage(ctx context.Context, search *schema.NotificationSearch) ([]*entity.Notification, int64, error)
|
||||
ClearUnRead(ctx context.Context, userID string, notificationType int) (err error)
|
||||
ClearIDUnRead(ctx context.Context, userID string, id string) (err error)
|
||||
GetByUserIdObjectIdTypeId(ctx context.Context, userID, objectID string, notificationType int) (*entity.Notification, bool, error)
|
||||
UpdateNotificationContent(ctx context.Context, notification *entity.Notification) (err error)
|
||||
GetById(ctx context.Context, id string) (*entity.Notification, bool, error)
|
||||
CountNotificationByUser(ctx context.Context, cond *entity.Notification) (int64, error)
|
||||
DeleteNotification(ctx context.Context, userID string) (err error)
|
||||
DeleteUserNotificationConfig(ctx context.Context, userID string) (err error)
|
||||
}
|
||||
|
||||
type NotificationCommon struct {
|
||||
data *data.Data
|
||||
notificationRepo NotificationRepo
|
||||
activityRepo activity_common.ActivityRepo
|
||||
followRepo activity_common.FollowRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
objectInfoService *object_info.ObjService
|
||||
notificationQueueService noticequeue.Service
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
func NewNotificationCommon(
|
||||
data *data.Data,
|
||||
notificationRepo NotificationRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
followRepo activity_common.FollowRepo,
|
||||
objectInfoService *object_info.ObjService,
|
||||
notificationQueueService noticequeue.Service,
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *NotificationCommon {
|
||||
notification := &NotificationCommon{
|
||||
data: data,
|
||||
notificationRepo: notificationRepo,
|
||||
activityRepo: activityRepo,
|
||||
followRepo: followRepo,
|
||||
userCommon: userCommon,
|
||||
objectInfoService: objectInfoService,
|
||||
notificationQueueService: notificationQueueService,
|
||||
userExternalLoginRepo: userExternalLoginRepo,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
notificationQueueService.RegisterHandler(notification.AddNotification)
|
||||
return notification
|
||||
}
|
||||
|
||||
// AddNotification
|
||||
// need set
|
||||
// LoginUserID
|
||||
// Type 1 inbox 2 achievement
|
||||
// [inbox] Activity
|
||||
// [achievement] Rank
|
||||
// ObjectInfo.Title
|
||||
// ObjectInfo.ObjectID
|
||||
// ObjectInfo.ObjectType
|
||||
func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.NotificationMsg) (err error) {
|
||||
if msg.Type == schema.NotificationTypeAchievement && plugin.RankAgentEnabled() {
|
||||
return nil
|
||||
}
|
||||
req := &schema.NotificationContent{
|
||||
TriggerUserID: msg.TriggerUserID,
|
||||
ReceiverUserID: msg.ReceiverUserID,
|
||||
ObjectInfo: schema.ObjectInfo{
|
||||
Title: msg.Title,
|
||||
ObjectID: uid.DeShortID(msg.ObjectID),
|
||||
ObjectType: msg.ObjectType,
|
||||
},
|
||||
NotificationAction: msg.NotificationAction,
|
||||
Type: msg.Type,
|
||||
}
|
||||
var questionID string // just for notify all followers
|
||||
var objInfo *schema.SimpleObjectInfo
|
||||
if msg.ObjectType == constant.BadgeAwardObjectType {
|
||||
req.ObjectInfo.Title = msg.Title
|
||||
objectMap := make(map[string]string)
|
||||
objectMap["badge_id"] = msg.ExtraInfo["badge_id"]
|
||||
req.ObjectInfo.ObjectMap = objectMap
|
||||
} else {
|
||||
objInfo, err = ns.objectInfoService.GetInfo(ctx, req.ObjectInfo.ObjectID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
} else {
|
||||
req.ObjectInfo.Title = objInfo.Title
|
||||
questionID = objInfo.QuestionID
|
||||
objectMap := make(map[string]string)
|
||||
objectMap["question"] = uid.DeShortID(objInfo.QuestionID)
|
||||
objectMap["answer"] = uid.DeShortID(objInfo.AnswerID)
|
||||
objectMap["comment"] = objInfo.CommentID
|
||||
req.ObjectInfo.ObjectMap = objectMap
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Type == schema.NotificationTypeAchievement {
|
||||
notificationInfo, exist, err := ns.notificationRepo.GetByUserIdObjectIdTypeId(ctx, req.ReceiverUserID, req.ObjectInfo.ObjectID, req.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get by user id object id type id error: %w", err)
|
||||
}
|
||||
rank, err := ns.activityRepo.GetUserIDObjectIDActivitySum(ctx, req.ReceiverUserID, req.ObjectInfo.ObjectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user id object id activity sum error: %w", err)
|
||||
}
|
||||
req.Rank = rank
|
||||
if exist {
|
||||
// modify notification
|
||||
updateContent := &schema.NotificationContent{}
|
||||
err := json.Unmarshal([]byte(notificationInfo.Content), updateContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal notification content error: %w", err)
|
||||
}
|
||||
updateContent.Rank = rank
|
||||
content, _ := json.Marshal(updateContent)
|
||||
notificationInfo.Content = string(content)
|
||||
err = ns.notificationRepo.UpdateNotificationContent(ctx, notificationInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update notification content error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
info := &entity.Notification{}
|
||||
now := time.Now()
|
||||
info.UserID = req.ReceiverUserID
|
||||
info.Type = req.Type
|
||||
info.IsRead = schema.NotificationNotRead
|
||||
info.Status = schema.NotificationStatusNormal
|
||||
info.CreatedAt = now
|
||||
info.UpdatedAt = now
|
||||
info.ObjectID = req.ObjectInfo.ObjectID
|
||||
|
||||
userBasicInfo, exist, err := ns.userCommon.GetUserBasicInfoByID(ctx, req.TriggerUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user basic info error: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
return fmt.Errorf("user not exist: %s", req.TriggerUserID)
|
||||
}
|
||||
req.UserInfo = userBasicInfo
|
||||
content, _ := json.Marshal(req)
|
||||
_, ok := constant.NotificationMsgTypeMapping[req.NotificationAction]
|
||||
if ok {
|
||||
info.MsgType = constant.NotificationMsgTypeMapping[req.NotificationAction]
|
||||
}
|
||||
info.Content = string(content)
|
||||
err = ns.notificationRepo.AddNotification(ctx, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add notification error: %w", err)
|
||||
}
|
||||
err = ns.addRedDot(ctx, info.UserID, msg.Type)
|
||||
if err != nil {
|
||||
log.Error("addRedDot Error", err.Error())
|
||||
}
|
||||
if req.ObjectInfo.ObjectType == constant.BadgeAwardObjectType {
|
||||
err = ns.AddBadgeAwardAlertCache(ctx, info.UserID, info.ID, req.ObjectInfo.ObjectMap["badge_id"])
|
||||
if err != nil {
|
||||
log.Error("AddBadgeAwardAlertCache Error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
go ns.SendNotificationToAllFollower(ctx, msg, questionID)
|
||||
|
||||
if msg.Type == schema.NotificationTypeInbox {
|
||||
ns.syncNotificationToPlugin(ctx, objInfo, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *NotificationCommon) addRedDot(ctx context.Context, userID string, noticeType int) error {
|
||||
var key string
|
||||
if noticeType == schema.NotificationTypeInbox {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeInbox, userID)
|
||||
} else {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeAchievement, userID)
|
||||
}
|
||||
_, exist, err := ns.data.Cache.GetInt64(ctx, key)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
if exist {
|
||||
if _, err := ns.data.Cache.Increase(ctx, key, 1); err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = ns.data.Cache.SetInt64(ctx, key, 1, constant.RedDotCacheTime)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *NotificationCommon) DecreaseRedDot(ctx context.Context, userID string, notificationType int) error {
|
||||
var key string
|
||||
if notificationType == schema.NotificationTypeInbox {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeInbox, userID)
|
||||
} else {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeAchievement, userID)
|
||||
}
|
||||
_, exist, err := ns.data.Cache.GetInt64(ctx, key)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
res, err := ns.data.Cache.Decrease(ctx, key, 1)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
if res <= 0 {
|
||||
return ns.DeleteRedDot(ctx, userID, notificationType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *NotificationCommon) DeleteRedDot(ctx context.Context, userID string, notificationType int) error {
|
||||
var key string
|
||||
if notificationType == schema.NotificationTypeInbox {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeInbox, userID)
|
||||
} else {
|
||||
key = fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeAchievement, userID)
|
||||
}
|
||||
err := ns.data.Cache.Del(ctx, key)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddBadgeAwardAlertCache add badge award alert cache
|
||||
func (ns *NotificationCommon) AddBadgeAwardAlertCache(ctx context.Context, userID, notificationID, badgeID string) (err error) {
|
||||
key := fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeBadgeAchievement, userID)
|
||||
cacheData, exist, err := ns.data.Cache.GetString(ctx, key)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
c := schema.NewRedDotBadgeAwardCache()
|
||||
c.AddBadgeAward(&schema.RedDotBadgeAward{
|
||||
NotificationID: notificationID,
|
||||
BadgeID: badgeID,
|
||||
})
|
||||
return ns.data.Cache.SetString(ctx, key, c.ToJSON(), constant.RedDotCacheTime)
|
||||
}
|
||||
c := schema.NewRedDotBadgeAwardCache()
|
||||
c.FromJSON(cacheData)
|
||||
c.AddBadgeAward(&schema.RedDotBadgeAward{
|
||||
NotificationID: notificationID,
|
||||
BadgeID: badgeID,
|
||||
})
|
||||
return ns.data.Cache.SetString(ctx, key, c.ToJSON(), constant.RedDotCacheTime)
|
||||
}
|
||||
|
||||
// RemoveBadgeAwardAlertCache remove badge award alert cache
|
||||
func (ns *NotificationCommon) RemoveBadgeAwardAlertCache(ctx context.Context, userID, notificationID string) (err error) {
|
||||
key := fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeBadgeAchievement, userID)
|
||||
cacheData, exist, err := ns.data.Cache.GetString(ctx, key)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
c := schema.NewRedDotBadgeAwardCache()
|
||||
c.FromJSON(cacheData)
|
||||
c.RemoveBadgeAward(notificationID)
|
||||
if len(c.BadgeAwardList) == 0 {
|
||||
return ns.data.Cache.Del(ctx, key)
|
||||
}
|
||||
return ns.data.Cache.SetString(ctx, key, c.ToJSON(), constant.RedDotCacheTime)
|
||||
}
|
||||
|
||||
// SendNotificationToAllFollower send notification to all followers
|
||||
func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context, msg *schema.NotificationMsg,
|
||||
questionID string) {
|
||||
if msg.NoNeedPushAllFollow || len(questionID) == 0 {
|
||||
return
|
||||
}
|
||||
if msg.NotificationAction != constant.NotificationUpdateQuestion &&
|
||||
msg.NotificationAction != constant.NotificationAnswerTheQuestion &&
|
||||
msg.NotificationAction != constant.NotificationUpdateAnswer &&
|
||||
msg.NotificationAction != constant.NotificationAcceptAnswer {
|
||||
return
|
||||
}
|
||||
condObjectID := msg.ObjectID
|
||||
if len(questionID) > 0 {
|
||||
condObjectID = uid.DeShortID(questionID)
|
||||
}
|
||||
userIDs, err := ns.followRepo.GetFollowUserIDs(ctx, condObjectID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Infof("send notification to all followers: %s %d", condObjectID, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
t := &schema.NotificationMsg{}
|
||||
_ = copier.Copy(t, msg)
|
||||
t.ReceiverUserID = userID
|
||||
t.TriggerUserID = msg.TriggerUserID
|
||||
t.NoNeedPushAllFollow = true
|
||||
ns.notificationQueueService.Send(ctx, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NotificationCommon) syncNotificationToPlugin(ctx context.Context, objInfo *schema.SimpleObjectInfo,
|
||||
msg *schema.NotificationMsg) {
|
||||
if objInfo == nil {
|
||||
return
|
||||
}
|
||||
siteInfo, err := ns.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("get site general info failed: %v", err)
|
||||
return
|
||||
}
|
||||
seoInfo, err := ns.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("get site seo info failed: %v", err)
|
||||
return
|
||||
}
|
||||
interfaceInfo, err := ns.siteInfoService.GetSiteInterface(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("get site interface info failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
objInfo.QuestionID = uid.DeShortID(objInfo.QuestionID)
|
||||
objInfo.AnswerID = uid.DeShortID(objInfo.AnswerID)
|
||||
pluginNotificationMsg := plugin.NotificationMessage{
|
||||
Type: plugin.NotificationType(msg.NotificationAction),
|
||||
ReceiverUserID: msg.ReceiverUserID,
|
||||
TriggerUserID: msg.TriggerUserID,
|
||||
QuestionTitle: objInfo.Title,
|
||||
}
|
||||
|
||||
if len(objInfo.QuestionID) > 0 {
|
||||
pluginNotificationMsg.QuestionUrl =
|
||||
display.QuestionURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title)
|
||||
}
|
||||
if len(objInfo.AnswerID) > 0 {
|
||||
pluginNotificationMsg.AnswerUrl =
|
||||
display.AnswerURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID)
|
||||
}
|
||||
if len(objInfo.CommentID) > 0 {
|
||||
pluginNotificationMsg.CommentUrl =
|
||||
display.CommentURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID, objInfo.CommentID)
|
||||
}
|
||||
|
||||
if len(msg.TriggerUserID) > 0 {
|
||||
triggerUser, exist, err := ns.userCommon.GetUserBasicInfoByID(ctx, msg.TriggerUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get trigger user basic info failed: %v", err)
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
pluginNotificationMsg.TriggerUserID = triggerUser.ID
|
||||
pluginNotificationMsg.TriggerUserDisplayName = triggerUser.DisplayName
|
||||
pluginNotificationMsg.TriggerUserUrl = display.UserURL(siteInfo.SiteUrl, triggerUser.Username)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pluginNotificationMsg.ReceiverLang) == 0 && len(msg.ReceiverUserID) > 0 {
|
||||
userInfo, _, _ := ns.userCommon.GetUserBasicInfoByID(ctx, msg.ReceiverUserID)
|
||||
if userInfo != nil {
|
||||
pluginNotificationMsg.ReceiverLang = userInfo.Language
|
||||
}
|
||||
// If receiver not set language, use site default language.
|
||||
if len(pluginNotificationMsg.ReceiverLang) == 0 || pluginNotificationMsg.ReceiverLang == translator.DefaultLangOption {
|
||||
pluginNotificationMsg.ReceiverLang = interfaceInfo.Language
|
||||
}
|
||||
}
|
||||
|
||||
externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, msg.ReceiverUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get user external login list failed for user %s: %v", msg.ReceiverUserID, err)
|
||||
} else if len(externalLogins) > 0 {
|
||||
pluginNotificationMsg.ReceiverExternalID = externalLogins[0].ExternalID
|
||||
}
|
||||
|
||||
_ = plugin.CallNotification(func(fn plugin.Notification) error {
|
||||
userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get user external login info failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
if exist {
|
||||
pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
|
||||
}
|
||||
fn.Notify(pluginNotificationMsg)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user