package logic import ( "context" "time" "spark-server/internal/module/square/entity" "spark-server/internal/pkg/errs" ) type NoticeView struct { ID int64 `json:"id"` Kind entity.NoticeKind `json:"kind"` From UserCard `json:"from"` PostID int64 `json:"post_id"` CommentID int64 `json:"comment_id,omitempty"` Read bool `json:"read"` CreatedAt int64 `json:"created_at"` } type NoticeList struct { Items []*NoticeView `json:"items"` NextCursor int64 `json:"next_cursor"` } type NoticeUnread struct { HasNew bool `json:"has_new"` LatestID int64 `json:"latest_id"` } type NoticeReadInput struct { Cursor int64 `json:"cursor" validate:"omitempty,gte=0" label:"field.square_notice_cursor"` } func (s *Service) ListNotices(ctx context.Context, userID int64, channel entity.Channel, cursor int64, limit int) (*NoticeList, error) { if !s.ready() || userID <= 0 { return nil, errs.ErrUnauthorized } channel = normalizeChannel(channel) if !channel.Valid() { return nil, errs.ErrInvalidArgument } limit = entity.ClampLimit(limit) rows, err := s.repo.ListNotices(ctx, userID, channel, cursor, limit) if err != nil { return nil, err } uids := make([]int64, 0, len(rows)) for _, n := range rows { uids = append(uids, n.FromUserID) } users := s.loadUsers(ctx, uids) remarks := s.loadRemarks(ctx, userID, uids) items := make([]*NoticeView, 0, len(rows)) next := int64(0) for _, n := range rows { items = append(items, &NoticeView{ ID: n.ID, Kind: n.Kind, From: cardOf(users[n.FromUserID], remarks[n.FromUserID]), PostID: n.PostID, CommentID: n.CommentID, Read: n.ReadAtMS > 0, CreatedAt: n.CreatedAtMS, }) next = n.ID } return &NoticeList{Items: items, NextCursor: next}, nil } func (s *Service) ReadNotices(ctx context.Context, userID int64, channel entity.Channel, in *NoticeReadInput) error { if !s.ready() || userID <= 0 { return errs.ErrUnauthorized } channel = normalizeChannel(channel) if !channel.Valid() { return errs.ErrInvalidArgument } cursor := int64(0) if in != nil { cursor = in.Cursor } if err := s.repo.MarkNoticesRead(ctx, userID, channel, cursor); err != nil { return err } if cursor > 0 { return s.repo.AdvanceNoticeRead(ctx, userID, channel, cursor) } latest, err := s.repo.LatestNoticeID(ctx, userID, channel) if err != nil { return err } if latest > 0 { return s.repo.AdvanceNoticeRead(ctx, userID, channel, latest) } return nil } func (s *Service) NoticeUnread(ctx context.Context, userID int64, channel entity.Channel) (*NoticeUnread, error) { if !s.ready() || userID <= 0 { return nil, errs.ErrUnauthorized } channel = normalizeChannel(channel) if !channel.Valid() { return nil, errs.ErrInvalidArgument } st, err := s.repo.GetNoticeState(ctx, userID, channel) if err != nil { return nil, err } latest, err := s.repo.LatestNoticeID(ctx, userID, channel) if err != nil { return nil, err } return &NoticeUnread{HasNew: latest > st.LastReadNoticeID, LatestID: latest}, nil } func (s *Service) notify(ctx context.Context, rows []*entity.Notice) error { out := make([]*entity.Notice, 0, len(rows)) now := time.Now().UnixMilli() for _, n := range rows { if n == nil || n.ToUserID <= 0 || n.ToUserID == n.FromUserID { continue } id, err := s.ids.NextID() if err != nil { return err } n.ID = id n.CreatedAtMS = now if n.Channel == "" { n.Channel = entity.ChannelSquare } out = append(out, n) } return s.repo.CreateNotices(ctx, out) } func (s *Service) newNotice(to, from int64, kind entity.NoticeKind, channel entity.Channel, postID, commentID int64) *entity.Notice { if to <= 0 || to == from { return nil } if channel == "" { channel = entity.ChannelSquare } return &entity.Notice{ToUserID: to, Channel: channel, FromUserID: from, Kind: kind, PostID: postID, CommentID: commentID} }