persona-community-1/services/persona-api/internal/adapter/memory/session.go
2026-02-23 10:21:29 +00:00

121 lines
2.7 KiB
Go

package memory
import (
"context"
"sync"
"time"
"git.threesix.ai/jordan/persona-community-1/services/persona-api/internal/domain"
"git.threesix.ai/jordan/persona-community-1/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
}