package repository import ( "context" "sync" "apps/backend/internal/module/scout/domain" ) type MemoryStore struct { mu sync.Mutex brands map[string]*domain.Brand products map[string]*domain.Product active map[int64]string posts map[string]*domain.Post hw map[string]*domain.Homework // key owner|theme crawler map[int64]*domain.CrawlerSession } func NewMemory() *MemoryStore { return &MemoryStore{ brands: map[string]*domain.Brand{}, products: map[string]*domain.Product{}, active: map[int64]string{}, posts: map[string]*domain.Post{}, hw: map[string]*domain.Homework{}, crawler: map[int64]*domain.CrawlerSession{}, } } func key(uid int64, theme string) string { return formatUID(uid) + "|" + theme } func formatUID(uid int64) string { if uid == 0 { return "0" } neg := uid < 0 if neg { uid = -uid } var b [32]byte i := len(b) for uid > 0 { i-- b[i] = byte('0' + uid%10) uid /= 10 } if neg { i-- b[i] = '-' } return string(b[i:]) } func (s *MemoryStore) SaveBrand(_ context.Context, b *domain.Brand) error { s.mu.Lock() defer s.mu.Unlock() cp := *b s.brands[b.ID] = &cp return nil } func (s *MemoryStore) GetBrand(_ context.Context, id string) (*domain.Brand, error) { s.mu.Lock() defer s.mu.Unlock() b, ok := s.brands[id] if !ok { return nil, domain.ErrNotFound } cp := *b return &cp, nil } func (s *MemoryStore) ListBrands(_ context.Context, ownerUID int64) ([]*domain.Brand, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Brand for _, b := range s.brands { if b.OwnerUID == ownerUID { cp := *b out = append(out, &cp) } } return out, nil } func (s *MemoryStore) DeleteBrand(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.brands[id]; !ok { return domain.ErrNotFound } delete(s.brands, id) return nil } func (s *MemoryStore) SaveProduct(_ context.Context, p *domain.Product) error { s.mu.Lock() defer s.mu.Unlock() cp := *p if p.MatchTags != nil { cp.MatchTags = append([]string(nil), p.MatchTags...) } if p.PainPoints != nil { cp.PainPoints = append([]string(nil), p.PainPoints...) } s.products[p.ID] = &cp return nil } func (s *MemoryStore) GetProduct(_ context.Context, id string) (*domain.Product, error) { s.mu.Lock() defer s.mu.Unlock() p, ok := s.products[id] if !ok { return nil, domain.ErrNotFound } return copyProduct(p), nil } func (s *MemoryStore) ListProducts(_ context.Context, ownerUID int64, brandID string) ([]*domain.Product, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Product for _, p := range s.products { if p.OwnerUID != ownerUID { continue } if brandID != "" && p.BrandID != brandID { continue } out = append(out, copyProduct(p)) } return out, nil } func (s *MemoryStore) DeleteProduct(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.products[id]; !ok { return domain.ErrNotFound } delete(s.products, id) return nil } func copyProduct(p *domain.Product) *domain.Product { cp := *p if p.MatchTags != nil { cp.MatchTags = append([]string(nil), p.MatchTags...) } if p.PainPoints != nil { cp.PainPoints = append([]string(nil), p.PainPoints...) } return &cp } func (s *MemoryStore) GetActiveBrandID(_ context.Context, ownerUID int64) (string, error) { s.mu.Lock() defer s.mu.Unlock() return s.active[ownerUID], nil } func (s *MemoryStore) SetActiveBrandID(_ context.Context, ownerUID int64, brandID string) error { s.mu.Lock() defer s.mu.Unlock() s.active[ownerUID] = brandID return nil } func (s *MemoryStore) SavePost(_ context.Context, p *domain.Post) error { s.mu.Lock() defer s.mu.Unlock() cp := *p s.posts[p.ID] = &cp return nil } func (s *MemoryStore) GetPost(_ context.Context, id string) (*domain.Post, error) { s.mu.Lock() defer s.mu.Unlock() p, ok := s.posts[id] if !ok { return nil, domain.ErrNotFound } cp := *p return &cp, nil } func (s *MemoryStore) ListPosts(_ context.Context, ownerUID int64, brandID string) ([]*domain.Post, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Post for _, p := range s.posts { if p.OwnerUID != ownerUID { continue } if brandID != "" && p.BrandID != brandID { continue } cp := *p out = append(out, &cp) } return out, nil } func (s *MemoryStore) DeletePost(_ context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.posts[id]; !ok { return domain.ErrNotFound } delete(s.posts, id) return nil } func (s *MemoryStore) DeletePostsByTheme(_ context.Context, ownerUID int64, themeKey string) error { s.mu.Lock() defer s.mu.Unlock() for id, p := range s.posts { if p.OwnerUID == ownerUID && p.ThemeKey == themeKey { delete(s.posts, id) } } return nil } func (s *MemoryStore) SaveHomework(_ context.Context, h *domain.Homework) error { s.mu.Lock() defer s.mu.Unlock() cp := *h s.hw[key(h.OwnerUID, h.ThemeKey)] = &cp return nil } func (s *MemoryStore) GetHomework(_ context.Context, ownerUID int64, themeKey string) (*domain.Homework, error) { s.mu.Lock() defer s.mu.Unlock() h, ok := s.hw[key(ownerUID, themeKey)] if !ok { return nil, domain.ErrNotFound } cp := *h return &cp, nil } func (s *MemoryStore) ListHomework(_ context.Context, ownerUID int64) ([]*domain.Homework, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Homework for _, h := range s.hw { if h.OwnerUID == ownerUID { cp := *h out = append(out, &cp) } } return out, nil } func (s *MemoryStore) DeleteHomework(_ context.Context, ownerUID int64, themeKey string) error { s.mu.Lock() defer s.mu.Unlock() k := key(ownerUID, themeKey) if _, ok := s.hw[k]; !ok { return domain.ErrNotFound } delete(s.hw, k) return nil } func (s *MemoryStore) GetCrawlerSession(_ context.Context, ownerUID int64) (*domain.CrawlerSession, error) { s.mu.Lock() defer s.mu.Unlock() c, ok := s.crawler[ownerUID] if !ok || c.Token == "" { return nil, domain.ErrNotFound } cp := *c return &cp, nil } func (s *MemoryStore) SetCrawlerSession(_ context.Context, sess *domain.CrawlerSession) error { s.mu.Lock() defer s.mu.Unlock() cp := *sess s.crawler[sess.OwnerUID] = &cp return nil } func (s *MemoryStore) ClearCrawlerSession(_ context.Context, ownerUID int64) error { s.mu.Lock() defer s.mu.Unlock() delete(s.crawler, ownerUID) return nil } var _ domain.Repository = (*MemoryStore)(nil)