package repository import ( "context" "fmt" "sync" "apps/backend/internal/module/usage/domain" ) type MemoryStore struct { mu sync.Mutex events []*domain.Event counters map[string]*domain.MonthlyCounter prefs map[int64]*domain.MemberPrefs purchases []*domain.Purchase } func NewMemory() *MemoryStore { return &MemoryStore{ counters: map[string]*domain.MonthlyCounter{}, prefs: map[int64]*domain.MemberPrefs{}, } } func memoryCounterID(uid int64, monthKey string) string { return fmt.Sprintf("%d:%s", uid, monthKey) } func (s *MemoryStore) ReservePlatform(_ context.Context, uid int64, monthKey, meter string, cost, totalLimit, meterLimit int) error { s.mu.Lock() defer s.mu.Unlock() id := memoryCounterID(uid, monthKey) counter := s.counters[id] if counter == nil { counter = &domain.MonthlyCounter{ID: id, UID: uid, MonthKey: monthKey, ByMeter: map[string]int{}} for _, event := range s.events { if event.UID == uid && event.MonthKey == monthKey && event.KeyMode == domain.KeyModePlatform { credits := event.Credits if minimum := domain.MeterCost(event.Meter); credits < minimum { credits = minimum } counter.TotalCredits += credits counter.ByMeter[event.Meter] += credits } } s.counters[id] = counter } if totalLimit >= 0 && counter.TotalCredits+cost > totalLimit { return domain.ErrQuotaExceeded } if meterLimit >= 0 && counter.ByMeter[meter]+cost > meterLimit { return domain.ErrMeterCapExceeded } counter.TotalCredits += cost counter.ByMeter[meter] += cost counter.UpdatedAt = domain.NowNano() return nil } func (s *MemoryStore) GetMonthlyCounter(_ context.Context, uid int64, monthKey string) (*domain.MonthlyCounter, error) { s.mu.Lock() defer s.mu.Unlock() counter := s.counters[memoryCounterID(uid, monthKey)] if counter == nil { return nil, domain.ErrNotFound } copyCounter := *counter copyCounter.ByMeter = make(map[string]int, len(counter.ByMeter)) for meter, credits := range counter.ByMeter { copyCounter.ByMeter[meter] = credits } return ©Counter, nil } func (s *MemoryStore) InsertEvent(_ context.Context, e *domain.Event) error { s.mu.Lock() defer s.mu.Unlock() cp := *e s.events = append(s.events, &cp) return nil } func (s *MemoryStore) ListEvents(_ context.Context, uid int64, monthKey, keyMode string, limit int) ([]*domain.Event, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Event for i := len(s.events) - 1; i >= 0; i-- { e := s.events[i] if e.UID != uid { continue } if monthKey != "" && e.MonthKey != monthKey { continue } if keyMode != "" && keyMode != "all" && e.KeyMode != keyMode { continue } cp := *e out = append(out, &cp) if limit > 0 && len(out) >= limit { break } } return out, nil } func (s *MemoryStore) ListEventsAll(_ context.Context, monthKey string, limit int) ([]*domain.Event, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Event for i := len(s.events) - 1; i >= 0; i-- { e := s.events[i] if monthKey != "" && e.MonthKey != monthKey { continue } cp := *e out = append(out, &cp) if limit > 0 && len(out) >= limit { break } } return out, nil } func (s *MemoryStore) ListEventsRange(_ context.Context, fromNano, toExclusiveNano int64) ([]*domain.Event, error) { s.mu.Lock() defer s.mu.Unlock() out := make([]*domain.Event, 0) for _, event := range s.events { if event.CreatedAt < fromNano || event.CreatedAt >= toExclusiveNano { continue } cp := *event out = append(out, &cp) } return out, nil } func (s *MemoryStore) GetPrefs(_ context.Context, uid int64) (*domain.MemberPrefs, error) { s.mu.Lock() defer s.mu.Unlock() if p, ok := s.prefs[uid]; ok { cp := *p return &cp, nil } return &domain.MemberPrefs{UID: uid, PlanID: domain.PlanFree}, nil } func (s *MemoryStore) SavePrefs(_ context.Context, p *domain.MemberPrefs) error { s.mu.Lock() defer s.mu.Unlock() cp := *p s.prefs[p.UID] = &cp return nil } func (s *MemoryStore) InsertPurchase(_ context.Context, p *domain.Purchase) error { s.mu.Lock() defer s.mu.Unlock() cp := *p s.purchases = append(s.purchases, &cp) return nil } func (s *MemoryStore) ListPurchases(_ context.Context, uid int64, limit int) ([]*domain.Purchase, error) { s.mu.Lock() defer s.mu.Unlock() var out []*domain.Purchase for i := len(s.purchases) - 1; i >= 0; i-- { p := s.purchases[i] if p.UID != uid { continue } cp := *p out = append(out, &cp) if limit > 0 && len(out) >= limit { break } } return out, nil } var _ domain.Repository = (*MemoryStore)(nil)