package store import ( "context" "database/sql" "errors" "fmt" "gitea.stevedudenhoeffer.com/steve/pansy/internal/domain" ) // CreateSession inserts a session row. The raw bearer token is never stored; // s.TokenHash is its sha256 (computed by the service layer). ExpiresAt is an // ISO-8601 UTC string, lexicographically comparable to the schema's timestamps. func (d *DB) CreateSession(ctx context.Context, s *domain.Session) error { _, err := d.sql.ExecContext(ctx, `INSERT INTO sessions (token_hash, user_id, expires_at) VALUES (?, ?, ?)`, s.TokenHash, s.UserID, s.ExpiresAt, ) if err != nil { return fmt.Errorf("store: insert session: %w", err) } return nil } // GetSession returns the session for a token hash, or domain.ErrNotFound. It // does not check expiry — that is the service layer's concern (which also // implements sliding renewal). func (d *DB) GetSession(ctx context.Context, tokenHash string) (*domain.Session, error) { var s domain.Session err := d.sql.QueryRowContext(ctx, `SELECT token_hash, user_id, expires_at, created_at FROM sessions WHERE token_hash = ?`, tokenHash, ).Scan(&s.TokenHash, &s.UserID, &s.ExpiresAt, &s.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return nil, domain.ErrNotFound } if err != nil { return nil, fmt.Errorf("store: get session: %w", err) } return &s, nil } // TouchSession moves a session's expiry forward (sliding-expiry renewal). func (d *DB) TouchSession(ctx context.Context, tokenHash, expiresAt string) error { _, err := d.sql.ExecContext(ctx, `UPDATE sessions SET expires_at = ? WHERE token_hash = ?`, expiresAt, tokenHash, ) if err != nil { return fmt.Errorf("store: touch session: %w", err) } return nil } // DeleteSession removes a session (logout, or lazy cleanup of an expired one). // Deleting a nonexistent session is not an error. func (d *DB) DeleteSession(ctx context.Context, tokenHash string) error { if _, err := d.sql.ExecContext(ctx, `DELETE FROM sessions WHERE token_hash = ?`, tokenHash, ); err != nil { return fmt.Errorf("store: delete session: %w", err) } return nil } // DeleteExpiredSessions removes every session that expired at or before now // (an ISO-8601 UTC string) and returns how many rows were deleted. func (d *DB) DeleteExpiredSessions(ctx context.Context, now string) (int64, error) { res, err := d.sql.ExecContext(ctx, `DELETE FROM sessions WHERE expires_at <= ?`, now, ) if err != nil { return 0, fmt.Errorf("store: delete expired sessions: %w", err) } n, err := res.RowsAffected() if err != nil { return 0, fmt.Errorf("store: expired sessions affected: %w", err) } return n, nil }