package model import ( "context" "sync" "spark-server/internal/module/inbox/entity" ) type dialog struct { unread map[int64]int64 read map[int64]int64 unreadCnt map[int64]int64 } // MemoryStore is an in-process inbox for tests. type MemoryStore struct { mu sync.Mutex byUser map[int64]*dialog previews map[int64][]byte } func NewMemoryStore() *MemoryStore { return &MemoryStore{byUser: make(map[int64]*dialog), previews: make(map[int64][]byte)} } func (s *MemoryStore) user(uid int64) *dialog { d, ok := s.byUser[uid] if !ok { d = &dialog{unread: map[int64]int64{}, read: map[int64]int64{}, unreadCnt: map[int64]int64{}} s.byUser[uid] = d } return d } func (s *MemoryStore) TouchUnread(_ context.Context, userID, conversationID, score int64) error { s.mu.Lock() defer s.mu.Unlock() d := s.user(userID) delete(d.read, conversationID) d.unread[conversationID] = score return nil } func (s *MemoryStore) TouchRead(_ context.Context, userID, conversationID, score int64) error { s.mu.Lock() defer s.mu.Unlock() d := s.user(userID) delete(d.unread, conversationID) d.read[conversationID] = score return nil } func (s *MemoryStore) IncrUnread(_ context.Context, userID, conversationID, delta int64) error { s.mu.Lock() defer s.mu.Unlock() d := s.user(userID) d.unreadCnt[conversationID] += delta return nil } func (s *MemoryStore) SetPreview(_ context.Context, conversationID int64, preview []byte) error { s.mu.Lock() defer s.mu.Unlock() s.previews[conversationID] = append([]byte(nil), preview...) return nil } func (s *MemoryStore) ListUpdatedSince(_ context.Context, userID, score int64, limit int) ([]entity.DialogHit, error) { s.mu.Lock() defer s.mu.Unlock() d := s.user(userID) var out []entity.DialogHit seen := map[int64]struct{}{} add := func(cid, sc int64) { if sc <= score { return } if _, ok := seen[cid]; ok { return } seen[cid] = struct{}{} out = append(out, entity.DialogHit{ConversationID: cid, Score: sc}) } for cid, sc := range d.unread { add(cid, sc) } for cid, sc := range d.read { add(cid, sc) } if limit > 0 && len(out) > limit { out = out[:limit] } return out, nil } func (s *MemoryStore) LoadDialogs(_ context.Context, userID int64, hits []entity.DialogHit) ([]entity.Dialog, error) { s.mu.Lock() defer s.mu.Unlock() out := make([]entity.Dialog, 0, len(hits)) for _, hit := range hits { out = append(out, entity.Dialog{ ConversationID: hit.ConversationID, Score: hit.Score, Unread: s.user(userID).unreadCnt[hit.ConversationID], Preview: append([]byte(nil), s.previews[hit.ConversationID]...), }) } return out, nil } func (s *MemoryStore) PurgeUser(_ context.Context, userID int64) error { if userID <= 0 { return nil } s.mu.Lock() defer s.mu.Unlock() delete(s.byUser, userID) return nil } func (s *MemoryStore) Preview(conversationID int64) []byte { s.mu.Lock() defer s.mu.Unlock() return append([]byte(nil), s.previews[conversationID]...) } func (s *MemoryStore) UnreadCount(userID, conversationID int64) int64 { s.mu.Lock() defer s.mu.Unlock() return s.user(userID).unreadCnt[conversationID] }