111 lines
4.3 KiB
Go
111 lines
4.3 KiB
Go
package notification
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// ReadService 执行后台账号与个人客户的幂等已读事务脚本。
|
|
type ReadService struct {
|
|
db *gorm.DB
|
|
now func() time.Time
|
|
}
|
|
|
|
// NewReadService 创建单条已读用例。
|
|
func NewReadService(db *gorm.DB) *ReadService {
|
|
return &ReadService{db: db, now: time.Now}
|
|
}
|
|
|
|
// MarkRead 仅首次更新当前接收人的未过期未读通知。
|
|
func (s *ReadService) MarkRead(ctx context.Context, recipientID, notificationID uint) error {
|
|
if recipientID == 0 || notificationID == 0 {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
now := s.now().UTC()
|
|
result := s.db.WithContext(ctx).Model(&model.Notification{}).
|
|
Where("id = ? AND recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)",
|
|
notificationID, constants.NotificationRecipientKindAccount, recipientID, false, now).
|
|
Updates(map[string]any{"is_read": true, "read_at": now})
|
|
if result.Error != nil {
|
|
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新通知已读状态失败")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MarkAllRead 将当前后台账号全部或指定类别的未过期通知幂等标记为已读。
|
|
func (s *ReadService) MarkAllRead(ctx context.Context, recipientID uint, request dto.NotificationReadAllRequest) (*dto.NotificationReadAllResponse, error) {
|
|
if recipientID == 0 || !isReadAllCategory(request.Category) {
|
|
return nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
now := s.now().UTC()
|
|
db := s.db.WithContext(ctx).Model(&model.Notification{}).
|
|
Where("recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)",
|
|
constants.NotificationRecipientKindAccount, recipientID, false, now)
|
|
if request.Category != "" {
|
|
db = db.Where("category = ?", request.Category)
|
|
}
|
|
result := db.Updates(map[string]any{"is_read": true, "read_at": now})
|
|
if result.Error != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新通知已读状态失败")
|
|
}
|
|
return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil
|
|
}
|
|
|
|
func isReadAllCategory(category string) bool {
|
|
switch category {
|
|
case "", constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry,
|
|
constants.NotificationCategorySync, constants.NotificationCategorySystem:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MarkPersonalRead 仅首次更新当前个人客户可见的未过期未读通知。
|
|
func (s *ReadService) MarkPersonalRead(ctx context.Context, customerID, notificationID uint) error {
|
|
if customerID == 0 || notificationID == 0 {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
now := s.now().UTC()
|
|
result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now).
|
|
Where("id = ? AND is_read = ?", notificationID, false).
|
|
Updates(map[string]any{"is_read": true, "read_at": now})
|
|
if result.Error != nil {
|
|
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新个人客户通知已读状态失败")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MarkAllPersonalRead 将当前个人客户可见的全部未过期通知幂等标记为已读。
|
|
func (s *ReadService) MarkAllPersonalRead(ctx context.Context, customerID uint) (*dto.NotificationReadAllResponse, error) {
|
|
if customerID == 0 {
|
|
return nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
now := s.now().UTC()
|
|
result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now).
|
|
Where("is_read = ?", false).
|
|
Updates(map[string]any{"is_read": true, "read_at": now})
|
|
if result.Error != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新个人客户通知已读状态失败")
|
|
}
|
|
return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil
|
|
}
|
|
|
|
func personalReadScope(db *gorm.DB, customerID uint, now time.Time) *gorm.DB {
|
|
return db.Where(`recipient_kind = ? AND recipient_id = ?
|
|
AND category IN ? AND type IN ? AND (expires_at IS NULL OR expires_at > ?)`,
|
|
constants.NotificationRecipientKindPersonalCustomer,
|
|
customerID,
|
|
[]string{constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry},
|
|
[]string{constants.NotificationTypePackageExpiring},
|
|
now,
|
|
)
|
|
}
|