package store import ( "math/rand" "sort" "strings" "sync" "time" "abuse_registration_poc/internal/models" "abuse_registration_poc/internal/utils" ) type MemoryStore struct { mu sync.RWMutex rows []models.Registration resetInterval time.Duration lastReset time.Time nextReset time.Time baseSize int } func NewMemoryStore(baseSize int, resetInterval time.Duration) *MemoryStore { if baseSize < 1 { baseSize = 546 } if resetInterval < time.Second { resetInterval = 10 * time.Minute } s := &MemoryStore{baseSize: baseSize, resetInterval: resetInterval} s.Reset() return s } func (s *MemoryStore) ResetInterval() time.Duration { return s.resetInterval } func (s *MemoryStore) Reset() { s.mu.Lock() defer s.mu.Unlock() now := time.Now().UTC() s.rows = generateBaseDataset(s.baseSize) s.lastReset = now s.nextReset = now.Add(s.resetInterval) } func (s *MemoryStore) Snapshot() ([]models.Registration, time.Time, time.Time) { s.resetIfNeeded() s.mu.RLock() defer s.mu.RUnlock() rows := make([]models.Registration, len(s.rows)) copy(rows, s.rows) return rows, s.lastReset, s.nextReset } func (s *MemoryStore) List(filter models.RegistrationFilter) []models.Registration { rows, _, _ := s.Snapshot() return applyFilter(rows, filter) } func (s *MemoryStore) Get(id string) (models.Registration, bool) { s.resetIfNeeded() s.mu.RLock() defer s.mu.RUnlock() for _, row := range s.rows { if row.ID == id { return row, true } } return models.Registration{}, false } func (s *MemoryStore) Create(input models.Registration) (models.Registration, error) { s.resetIfNeeded() if err := models.NormalizeRegistration(&input); err != nil { return models.Registration{}, err } s.mu.Lock() defer s.mu.Unlock() input.ID = utils.NewUUID() s.rows = append(s.rows, input) return input, nil } func (s *MemoryStore) Replace(id string, input models.Registration) (models.Registration, bool, error) { s.resetIfNeeded() if err := models.NormalizeRegistration(&input); err != nil { return models.Registration{}, false, err } s.mu.Lock() defer s.mu.Unlock() for i := range s.rows { if s.rows[i].ID == id { input.ID = id s.rows[i] = input return input, true, nil } } return models.Registration{}, false, nil } func (s *MemoryStore) Delete(id string) bool { s.resetIfNeeded() s.mu.Lock() defer s.mu.Unlock() for i := range s.rows { if s.rows[i].ID == id { s.rows = append(s.rows[:i], s.rows[i+1:]...) return true } } return false } func (s *MemoryStore) resetIfNeeded() { s.mu.RLock() due := time.Now().UTC().After(s.nextReset) s.mu.RUnlock() if due { s.Reset() } } func applyFilter(rows []models.Registration, filter models.RegistrationFilter) []models.Registration { location := strings.ToLower(strings.TrimSpace(filter.Location)) abuseType := strings.ToLower(strings.TrimSpace(filter.AbuseType)) gender := strings.ToLower(strings.TrimSpace(filter.Gender)) status := strings.ToLower(strings.TrimSpace(filter.Status)) search := strings.ToLower(strings.TrimSpace(filter.Search)) out := make([]models.Registration, 0, len(rows)) for _, row := range rows { if location != "" && strings.ToLower(row.Location) != location { continue } if abuseType != "" && strings.ToLower(row.AbuseType) != abuseType { continue } if gender != "" && strings.ToLower(row.Gender) != gender { continue } if status != "" && strings.ToLower(row.Status) != status { continue } if filter.HasFrom || filter.HasTo { registeredAt, err := time.Parse(time.RFC3339, row.RegisteredAt) if err != nil { continue } if filter.HasFrom && registeredAt.Before(filter.From) { continue } if filter.HasTo && registeredAt.After(filter.To) { continue } } if search != "" && !strings.Contains(strings.ToLower(row.ID+" "+row.Location+" "+row.AbuseType+" "+row.Gender+" "+row.Status), search) { continue } out = append(out, row) } sort.SliceStable(out, func(i, j int) bool { return out[i].RegisteredAt < out[j].RegisteredAt }) if filter.Offset > 0 { if filter.Offset >= len(out) { return []models.Registration{} } out = out[filter.Offset:] } if filter.Limit > 0 && filter.Limit < len(out) { out = out[:filter.Limit] } return out } func generateBaseDataset(count int) []models.Registration { seed := rand.New(rand.NewSource(19881991)) locations := models.FaroeLocations rows := make([]models.Registration, 0, count) for i := 0; i < count; i++ { location := locations[i%len(locations)] if i >= len(locations) { location = locations[seed.Intn(len(locations))] } rows = append(rows, models.Registration{ ID: utils.NewUUID(), RegisteredAt: randomRegistrationTime(seed).Format(time.RFC3339), Gender: models.Genders[seed.Intn(len(models.Genders))], Location: location, AbuseType: models.AbuseCategories[seed.Intn(len(models.AbuseCategories))], Status: models.Statuses[seed.Intn(len(models.Statuses))], }) } return rows } func randomRegistrationTime(seed *rand.Rand) time.Time { start := time.Date(1988, 1, 1, 0, 0, 0, 0, time.UTC) end := time.Date(1991, 12, 31, 23, 59, 59, 0, time.UTC) span := end.Unix() - start.Unix() return time.Unix(start.Unix()+seed.Int63n(span), 0).UTC() }