package model import ( "context" "sort" "sync" "spark-server/internal/module/favorite/entity" "spark-server/internal/pkg/errs" ) type userMsgKey struct { UserID int64 MsgID int64 } // MemoryRepo is an in-process favorite store for tests and empty-DSN bring-up. type MemoryRepo struct { mu sync.Mutex byID map[int64]*entity.Favorite byPair map[userMsgKey]int64 } func NewMemoryRepo() *MemoryRepo { return &MemoryRepo{byID: make(map[int64]*entity.Favorite), byPair: make(map[userMsgKey]int64)} } func (m *MemoryRepo) Get(_ context.Context, id int64) (*entity.Favorite, error) { m.mu.Lock() defer m.mu.Unlock() row, ok := m.byID[id] if !ok { return nil, errs.ErrNotFound } return cloneFav(row), nil } func (m *MemoryRepo) GetByUserMsg(_ context.Context, userID, msgID int64) (*entity.Favorite, error) { m.mu.Lock() defer m.mu.Unlock() id, ok := m.byPair[userMsgKey{UserID: userID, MsgID: msgID}] if !ok { return nil, errs.ErrNotFound } return cloneFav(m.byID[id]), nil } func (m *MemoryRepo) Create(_ context.Context, fav *entity.Favorite) error { if fav == nil || fav.ID <= 0 || fav.UserID <= 0 { return errs.ErrInvalidArgument } m.mu.Lock() defer m.mu.Unlock() key := userMsgKey{UserID: fav.UserID, MsgID: fav.MsgID} if _, ok := m.byPair[key]; ok { return errs.ErrDuplicate } if _, ok := m.byID[fav.ID]; ok { return errs.ErrDuplicate } m.byID[fav.ID] = cloneFav(fav) m.byPair[key] = fav.ID return nil } func (m *MemoryRepo) ListByUser(_ context.Context, userID int64, limit int) ([]*entity.Favorite, error) { m.mu.Lock() defer m.mu.Unlock() limit = entity.ClampLimit(limit) out := make([]*entity.Favorite, 0) for _, row := range m.byID { if row.UserID == userID { out = append(out, cloneFav(row)) } } sort.Slice(out, func(i, j int) bool { return out[i].ID > out[j].ID }) if len(out) > limit { out = out[:limit] } return out, nil } func (m *MemoryRepo) ListAllByUser(_ context.Context, userID int64) ([]*entity.Favorite, error) { m.mu.Lock() defer m.mu.Unlock() out := make([]*entity.Favorite, 0) for _, row := range m.byID { if row.UserID == userID { out = append(out, cloneFav(row)) } } sort.Slice(out, func(i, j int) bool { return out[i].ID > out[j].ID }) return out, nil } func (m *MemoryRepo) Delete(_ context.Context, userID, id int64) error { m.mu.Lock() defer m.mu.Unlock() row, ok := m.byID[id] if !ok || row.UserID != userID { return errs.ErrNotFound } delete(m.byID, id) delete(m.byPair, userMsgKey{UserID: row.UserID, MsgID: row.MsgID}) return nil } func cloneFav(f *entity.Favorite) *entity.Favorite { if f == nil { return nil } cp := *f if f.Content != nil { cp.Content = append([]byte(nil), f.Content...) } return &cp }