package memory import ( "context" "sort" "strings" "sync" "time" "telesrv/internal/domain" ) // ReservedUsernameStore is the in-memory operator username blocklist. type ReservedUsernameStore struct { mu sync.Mutex entries map[string]domain.ReservedUsername // keyed by username_lower } // NewReservedUsernameStore creates an empty blocklist. func NewReservedUsernameStore() *ReservedUsernameStore { return &ReservedUsernameStore{entries: make(map[string]domain.ReservedUsername)} } func (s *ReservedUsernameStore) IsReserved(_ context.Context, usernameLower string) (bool, error) { if s == nil { return false, nil } usernameLower = strings.ToLower(strings.TrimSpace(usernameLower)) if usernameLower == "" { return false, nil } s.mu.Lock() defer s.mu.Unlock() _, ok := s.entries[usernameLower] return ok, nil } func (s *ReservedUsernameStore) ReserveUsername(_ context.Context, username, reason, actor string) (bool, error) { username = strings.TrimSpace(username) lower := strings.ToLower(username) if lower == "" { return false, domain.ErrUsernameInvalid } s.mu.Lock() defer s.mu.Unlock() if _, ok := s.entries[lower]; ok { return false, nil } s.entries[lower] = domain.ReservedUsername{Username: username, Reason: reason, Actor: actor, CreatedAt: time.Now().UTC()} return true, nil } func (s *ReservedUsernameStore) UnreserveUsername(_ context.Context, username string) (bool, error) { lower := strings.ToLower(strings.TrimSpace(username)) if lower == "" { return false, domain.ErrUsernameInvalid } s.mu.Lock() defer s.mu.Unlock() if _, ok := s.entries[lower]; !ok { return false, nil } delete(s.entries, lower) return true, nil } func (s *ReservedUsernameStore) ReservedUsernames(_ context.Context, filter domain.ReservedUsernameFilter) ([]domain.ReservedUsername, error) { s.mu.Lock() defer s.mu.Unlock() q := strings.ToLower(strings.TrimSpace(filter.Query)) out := make([]domain.ReservedUsername, 0, len(s.entries)) for key, entry := range s.entries { if q != "" && !strings.HasPrefix(key, q) { continue } out = append(out, entry) } sort.Slice(out, func(i, j int) bool { if !out[i].CreatedAt.Equal(out[j].CreatedAt) { return out[i].CreatedAt.After(out[j].CreatedAt) } return strings.ToLower(out[i].Username) < strings.ToLower(out[j].Username) }) limit := filter.Limit if limit <= 0 || limit > 500 { limit = 100 } offset := filter.Offset if offset < 0 { offset = 0 } if offset >= len(out) { return []domain.ReservedUsername{}, nil } end := offset + limit if end > len(out) { end = len(out) } return out[offset:end], nil }