package store import ( "errors" "sync" "time" ) var ( ErrNotFound = errors.New("not found") ErrAlreadyExists = errors.New("already exists") ) type MemoryStore struct { mu sync.RWMutex users map[string]User challenges map[string]Challenge sessions map[string]Session invitations map[string]Invitation collections map[string]Collection features map[string]Feature } func NewMemoryStore() *MemoryStore { return &MemoryStore{ users: make(map[string]User), challenges: make(map[string]Challenge), sessions: make(map[string]Session), invitations: make(map[string]Invitation), collections: make(map[string]Collection), features: make(map[string]Feature), } } func (s *MemoryStore) UpsertUser(user User) { s.mu.Lock() defer s.mu.Unlock() s.users[user.PublicKey] = user } func (s *MemoryStore) GetUser(publicKey string) (User, error) { s.mu.RLock() defer s.mu.RUnlock() user, ok := s.users[publicKey] if !ok { return User{}, ErrNotFound } return user, nil } func (s *MemoryStore) CreateChallenge(ch Challenge) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.challenges[ch.Nonce]; exists { return ErrAlreadyExists } s.challenges[ch.Nonce] = ch return nil } func (s *MemoryStore) GetChallenge(nonce string) (Challenge, error) { s.mu.RLock() defer s.mu.RUnlock() ch, ok := s.challenges[nonce] if !ok { return Challenge{}, ErrNotFound } return ch, nil } func (s *MemoryStore) MarkChallengeUsed(nonce string) error { s.mu.Lock() defer s.mu.Unlock() ch, ok := s.challenges[nonce] if !ok { return ErrNotFound } ch.Used = true s.challenges[nonce] = ch return nil } func (s *MemoryStore) SaveSession(session Session) { s.mu.Lock() defer s.mu.Unlock() s.sessions[session.Token] = session } func (s *MemoryStore) GetSession(token string) (Session, error) { s.mu.RLock() defer s.mu.RUnlock() session, ok := s.sessions[token] if !ok { return Session{}, ErrNotFound } return session, nil } func (s *MemoryStore) SaveInvitation(inv Invitation) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.invitations[inv.JTI]; exists { return ErrAlreadyExists } s.invitations[inv.JTI] = inv return nil } func (s *MemoryStore) GetInvitation(jti string) (Invitation, error) { s.mu.RLock() defer s.mu.RUnlock() inv, ok := s.invitations[jti] if !ok { return Invitation{}, ErrNotFound } return inv, nil } func (s *MemoryStore) IncrementInvitationUsage(jti string) error { s.mu.Lock() defer s.mu.Unlock() inv, ok := s.invitations[jti] if !ok { return ErrNotFound } inv.UsedCount++ s.invitations[jti] = inv return nil } func (s *MemoryStore) SaveCollection(c Collection) { s.mu.Lock() defer s.mu.Unlock() s.collections[c.ID] = c } func (s *MemoryStore) ListCollectionsByOwner(owner string) []Collection { s.mu.RLock() defer s.mu.RUnlock() result := make([]Collection, 0) for _, c := range s.collections { if c.OwnerKey == owner { result = append(result, c) } } return result } func (s *MemoryStore) GetCollection(id string) (Collection, error) { s.mu.RLock() defer s.mu.RUnlock() c, ok := s.collections[id] if !ok { return Collection{}, ErrNotFound } return c, nil } func (s *MemoryStore) DeleteCollection(id string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.collections[id]; !ok { return ErrNotFound } for fid, f := range s.features { if f.CollectionID == id { delete(s.features, fid) } } delete(s.collections, id) return nil } func (s *MemoryStore) SaveFeature(f Feature) { s.mu.Lock() defer s.mu.Unlock() s.features[f.ID] = f } func (s *MemoryStore) ListFeaturesByCollection(collectionID string) []Feature { s.mu.RLock() defer s.mu.RUnlock() result := make([]Feature, 0) for _, f := range s.features { if f.CollectionID == collectionID { result = append(result, f) } } return result } func (s *MemoryStore) GetFeature(featureID string) (Feature, error) { s.mu.RLock() defer s.mu.RUnlock() f, ok := s.features[featureID] if !ok { return Feature{}, ErrNotFound } return f, nil } func (s *MemoryStore) DeleteFeature(featureID string) error { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.features[featureID]; !ok { return ErrNotFound } delete(s.features, featureID) return nil } func (s *MemoryStore) PruneExpired(now time.Time) { s.mu.Lock() defer s.mu.Unlock() for nonce, ch := range s.challenges { if ch.ExpiresAt.Before(now) { delete(s.challenges, nonce) } } for token, sess := range s.sessions { if sess.ExpiresAt.Before(now) { delete(s.sessions, token) } } for jti, inv := range s.invitations { if inv.ExpiresAt.Before(now) { delete(s.invitations, jti) } } } func (s *MemoryStore) SaveUserLogin(ul UserLogin) { // In-memory store: no-op for login history (persistence only in Postgres) }