package memory import ( "context" "sync" "time" "git.threesix.ai/jordan/persona-community-2/services/persona-api/internal/domain" "git.threesix.ai/jordan/persona-community-2/services/persona-api/internal/port" ) // Compile-time interface check. var _ port.SessionRepository = (*SessionRepository)(nil) // SessionRepository is an in-memory session store for standalone development. type SessionRepository struct { mu sync.RWMutex sessions map[domain.SessionID]*domain.Session } // NewSessionRepository creates a new in-memory session repository. func NewSessionRepository() *SessionRepository { return &SessionRepository{ sessions: make(map[domain.SessionID]*domain.Session), } } func (r *SessionRepository) copySession(s *domain.Session) *domain.Session { cp := *s return &cp } func (r *SessionRepository) Create(_ context.Context, session *domain.Session) error { r.mu.Lock() defer r.mu.Unlock() r.sessions[session.ID] = r.copySession(session) return nil } func (r *SessionRepository) Get(_ context.Context, id domain.SessionID) (*domain.Session, error) { r.mu.RLock() defer r.mu.RUnlock() s, ok := r.sessions[id] if !ok { return nil, domain.ErrSessionNotFound } return r.copySession(s), nil } func (r *SessionRepository) ListByUser(_ context.Context, userID domain.UserID) ([]domain.Session, error) { r.mu.RLock() defer r.mu.RUnlock() now := time.Now() var result []domain.Session for _, s := range r.sessions { if s.UserID == userID && s.RevokedAt == nil && s.ExpiresAt.After(now) { result = append(result, *r.copySession(s)) } } return result, nil } func (r *SessionRepository) UpdateLastActive(_ context.Context, id domain.SessionID) error { r.mu.Lock() defer r.mu.Unlock() s, ok := r.sessions[id] if !ok { return domain.ErrSessionNotFound } s.LastActiveAt = time.Now() return nil } func (r *SessionRepository) Revoke(_ context.Context, id domain.SessionID) error { r.mu.Lock() defer r.mu.Unlock() s, ok := r.sessions[id] if !ok { return domain.ErrSessionNotFound } now := time.Now() s.RevokedAt = &now return nil } func (r *SessionRepository) RevokeAllForUser(_ context.Context, userID domain.UserID, exceptID *domain.SessionID) error { r.mu.Lock() defer r.mu.Unlock() now := time.Now() for _, s := range r.sessions { if s.UserID == userID && s.RevokedAt == nil { if exceptID != nil && s.ID == *exceptID { continue } s.RevokedAt = &now } } return nil } func (r *SessionRepository) DeleteExpired(_ context.Context) (int, error) { r.mu.Lock() defer r.mu.Unlock() now := time.Now() deleted := 0 for id, s := range r.sessions { if now.After(s.ExpiresAt) { delete(r.sessions, id) deleted++ } } return deleted, nil }