package postgres import ( "context" "errors" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/tdvorak/seen/backend/internal/domain" ) var ErrUserAlreadyExists = errors.New("user already exists") type AuthRepository struct { pool *pgxpool.Pool } func NewAuthRepository(pool *pgxpool.Pool) *AuthRepository { return &AuthRepository{pool: pool} } func (r *AuthRepository) CreateUser(ctx context.Context, user domain.User) error { _, err := r.pool.Exec( ctx, `INSERT INTO users (id, email, display_name, role, password_hash, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`, user.ID, strings.ToLower(strings.TrimSpace(user.Email)), user.DisplayName, user.Role, user.PasswordHash, user.CreatedAt, user.UpdatedAt, ) if err != nil && strings.Contains(err.Error(), "duplicate key") { return ErrUserAlreadyExists } return err } func (r *AuthRepository) FindUserByEmail(ctx context.Context, email string) (*domain.User, error) { row := r.pool.QueryRow( ctx, `SELECT id, email, display_name, role, password_hash, created_at, updated_at FROM users WHERE email = $1`, strings.ToLower(strings.TrimSpace(email)), ) var user domain.User if err := row.Scan( &user.ID, &user.Email, &user.DisplayName, &user.Role, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &user, nil } func (r *AuthRepository) FindUserByID(ctx context.Context, userID uuid.UUID) (*domain.User, error) { row := r.pool.QueryRow( ctx, `SELECT id, email, display_name, role, password_hash, created_at, updated_at FROM users WHERE id = $1`, userID, ) var user domain.User if err := row.Scan( &user.ID, &user.Email, &user.DisplayName, &user.Role, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &user, nil } func (r *AuthRepository) CreateSession(ctx context.Context, session domain.Session) error { _, err := r.pool.Exec( ctx, `INSERT INTO sessions (id, user_id, refresh_token, user_agent, ip, expires_at, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`, session.ID, session.UserID, session.RefreshToken, session.UserAgent, session.IP, session.ExpiresAt, session.CreatedAt, ) return err } func (r *AuthRepository) FindSessionByRefreshToken(ctx context.Context, refreshToken string) (*domain.Session, error) { row := r.pool.QueryRow( ctx, `SELECT id, user_id, refresh_token, user_agent, ip, expires_at, revoked_at, created_at FROM sessions WHERE refresh_token = $1`, refreshToken, ) var session domain.Session if err := row.Scan( &session.ID, &session.UserID, &session.RefreshToken, &session.UserAgent, &session.IP, &session.ExpiresAt, &session.RevokedAt, &session.CreatedAt, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &session, nil } func (r *AuthRepository) RevokeSession(ctx context.Context, sessionID uuid.UUID) error { _, err := r.pool.Exec( ctx, `UPDATE sessions SET revoked_at = now() WHERE id = $1`, sessionID, ) return err }