package repository import ( "context" "sort" "sync" "apps/backend/internal/module/studio/domain" ) type MemoryStore struct { mu sync.Mutex personas map[string]*domain.Persona active map[int64]string plays map[string]*domain.Play outbox map[string]*domain.OutboxBundle ownPosts map[string]*domain.OwnPost mentions map[string]*domain.Mention syncMeta map[int64]*domain.SyncMeta } func NewMemory() *MemoryStore { return &MemoryStore{ personas: map[string]*domain.Persona{}, active: map[int64]string{}, plays: map[string]*domain.Play{}, outbox: map[string]*domain.OutboxBundle{}, ownPosts: map[string]*domain.OwnPost{}, mentions: map[string]*domain.Mention{}, syncMeta: map[int64]*domain.SyncMeta{}, } } func (s *MemoryStore) SavePersona(_ context.Context, p *domain.Persona) error { s.mu.Lock() defer s.mu.Unlock() cp := *p s.personas[p.ID] = &cp return nil } func (s *MemoryStore) GetPersona(_ context.Context, id string) (*domain.Persona, error) { s.mu.Lock() defer s.mu.Unlock() p, ok := s.personas[id] if !ok { return nil, domain.ErrNotFound } cp := *p return &cp, nil } func (s *MemoryStore) ListPersonas(_ context.Context, ownerUID int64) ([]*domain.Persona, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Persona for _, p := range s.personas { if p.OwnerUID == ownerUID { cp := *p out = append(out, &cp) } } sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt }) return out, nil } func (s *MemoryStore) DeletePersona(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.personas[id]; !ok { return domain.ErrNotFound } delete(s.personas, id) return nil } func (s *MemoryStore) GetActivePersonaID(_ context.Context, ownerUID int64) (string, error) { s.mu.Lock() defer s.mu.Unlock() return s.active[ownerUID], nil } func (s *MemoryStore) SetActivePersonaID(_ context.Context, ownerUID int64, personaID string) error { s.mu.Lock() defer s.mu.Unlock() s.active[ownerUID] = personaID return nil } func (s *MemoryStore) SavePlay(_ context.Context, p *domain.Play) error { s.mu.Lock() defer s.mu.Unlock() cp := *p if p.Steps != nil { cp.Steps = append([]domain.PlayStep(nil), p.Steps...) } if p.CastAccountIDs != nil { cp.CastAccountIDs = append([]string(nil), p.CastAccountIDs...) } if p.TargetExternal != nil { te := *p.TargetExternal cp.TargetExternal = &te } s.plays[p.ID] = &cp return nil } func (s *MemoryStore) GetPlay(_ context.Context, id string) (*domain.Play, error) { s.mu.Lock() defer s.mu.Unlock() p, ok := s.plays[id] if !ok { return nil, domain.ErrNotFound } return copyPlay(p), nil } func (s *MemoryStore) ListPlays(_ context.Context, ownerUID int64) ([]*domain.Play, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Play for _, p := range s.plays { if p.OwnerUID == ownerUID { out = append(out, copyPlay(p)) } } sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt }) return out, nil } func (s *MemoryStore) DeletePlay(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.plays[id]; !ok { return domain.ErrNotFound } delete(s.plays, id) return nil } func copyPlay(p *domain.Play) *domain.Play { cp := *p if p.Steps != nil { cp.Steps = append([]domain.PlayStep(nil), p.Steps...) } if p.CastAccountIDs != nil { cp.CastAccountIDs = append([]string(nil), p.CastAccountIDs...) } if p.TargetExternal != nil { te := *p.TargetExternal cp.TargetExternal = &te } return &cp } func (s *MemoryStore) SaveOutbox(_ context.Context, b *domain.OutboxBundle) error { s.mu.Lock() defer s.mu.Unlock() cp := *b if b.Steps != nil { cp.Steps = append([]domain.OutboxStep(nil), b.Steps...) } s.outbox[b.ID] = &cp return nil } func (s *MemoryStore) GetOutbox(_ context.Context, id string) (*domain.OutboxBundle, error) { s.mu.Lock() defer s.mu.Unlock() b, ok := s.outbox[id] if !ok { return nil, domain.ErrNotFound } return copyOutbox(b), nil } func (s *MemoryStore) ListOutbox(_ context.Context, ownerUID int64) ([]*domain.OutboxBundle, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.OutboxBundle for _, b := range s.outbox { if b.OwnerUID == ownerUID { out = append(out, copyOutbox(b)) } } sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt }) return out, nil } func (s *MemoryStore) DeleteOutbox(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.outbox[id]; !ok { return domain.ErrNotFound } delete(s.outbox, id) return nil } func (s *MemoryStore) ListAllOutbox(_ context.Context) ([]*domain.OutboxBundle, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.OutboxBundle for _, b := range s.outbox { out = append(out, copyOutbox(b)) } return out, nil } func copyOutbox(b *domain.OutboxBundle) *domain.OutboxBundle { cp := *b if b.Steps != nil { cp.Steps = append([]domain.OutboxStep(nil), b.Steps...) } return &cp } func (s *MemoryStore) SaveOwnPost(_ context.Context, p *domain.OwnPost) error { s.mu.Lock() defer s.mu.Unlock() cp := *p if p.Replies != nil { cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...) } s.ownPosts[p.ID] = &cp return nil } func (s *MemoryStore) GetOwnPost(_ context.Context, id string) (*domain.OwnPost, error) { s.mu.Lock() defer s.mu.Unlock() p, ok := s.ownPosts[id] if !ok { return nil, domain.ErrNotFound } cp := *p if p.Replies != nil { cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...) } return &cp, nil } func (s *MemoryStore) DeleteOwnPost(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() delete(s.ownPosts, id) return nil } func (s *MemoryStore) ListOwnPosts(_ context.Context, ownerUID int64, accountID string) ([]*domain.OwnPost, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.OwnPost for _, p := range s.ownPosts { if p.OwnerUID != ownerUID { continue } if accountID != "" && p.AccountID != accountID { continue } cp := *p if p.Replies != nil { cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...) } out = append(out, &cp) } sort.Slice(out, func(i, j int) bool { return out[i].PublishedAt > out[j].PublishedAt }) return out, nil } func (s *MemoryStore) GetSyncMeta(_ context.Context, ownerUID int64) (*domain.SyncMeta, error) { s.mu.Lock() defer s.mu.Unlock() m, ok := s.syncMeta[ownerUID] if !ok { return &domain.SyncMeta{OwnerUID: ownerUID}, nil } cp := *m return &cp, nil } func (s *MemoryStore) SetSyncMeta(_ context.Context, m *domain.SyncMeta) error { s.mu.Lock() defer s.mu.Unlock() cp := *m s.syncMeta[m.OwnerUID] = &cp return nil } func (s *MemoryStore) SaveMention(_ context.Context, m *domain.Mention) error { s.mu.Lock() defer s.mu.Unlock() cp := *m s.mentions[m.ID] = &cp return nil } func (s *MemoryStore) GetMention(_ context.Context, id string) (*domain.Mention, error) { s.mu.Lock() defer s.mu.Unlock() m, ok := s.mentions[id] if !ok { return nil, domain.ErrNotFound } cp := *m return &cp, nil } func (s *MemoryStore) ListMentions(_ context.Context, ownerUID int64, accountID string) ([]*domain.Mention, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Mention for _, m := range s.mentions { if m.OwnerUID != ownerUID { continue } if accountID != "" && m.AccountID != accountID { continue } cp := *m out = append(out, &cp) } sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt > out[j].CreatedAt }) return out, nil } var _ domain.Repository = (*MemoryStore)(nil)