package storage import ( "context" "errors" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "github.com/alchemistkay/guestguard/internal/domain" ) type TokenRepo struct { pool *pgxpool.Pool } func NewTokenRepo(db *DB) *TokenRepo { return &TokenRepo{pool: db.Pool} } type CreateTokenParams struct { GuestID uuid.UUID TokenHash string ExpiresAt time.Time } func (r *TokenRepo) Create(ctx context.Context, p CreateTokenParams) (*domain.Token, error) { const q = ` INSERT INTO tokens (guest_id, token_hash, expires_at, status) VALUES ($1, $2, $3, 'active') RETURNING id, guest_id, token_hash, expires_at, status, used_at, created_at ` row := r.pool.QueryRow(ctx, q, p.GuestID, p.TokenHash, p.ExpiresAt) tk, err := scanToken(row) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return nil, errors.New("guest already has a token") } return nil, err } return tk, nil } func (r *TokenRepo) GetByHash(ctx context.Context, hash string) (*domain.Token, error) { const q = ` SELECT id, guest_id, token_hash, expires_at, status, used_at, created_at FROM tokens WHERE token_hash = $1 ` tk, err := scanToken(r.pool.QueryRow(ctx, q, hash)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, domain.ErrTokenNotFound } return nil, err } return tk, nil } func (r *TokenRepo) MarkUsed(ctx context.Context, id uuid.UUID) error { tag, err := r.pool.Exec(ctx, ` UPDATE tokens SET status = 'used', used_at = now() WHERE id = $1 AND status = 'active' `, id) if err != nil { return err } if tag.RowsAffected() == 0 { return domain.ErrTokenNotFound } return nil } func scanToken(s rowScanner) (*domain.Token, error) { var tk domain.Token err := s.Scan( &tk.ID, &tk.GuestID, &tk.TokenHash, &tk.ExpiresAt, &tk.Status, &tk.UsedAt, &tk.CreatedAt, ) if err != nil { return nil, err } return &tk, nil }