package auth import ( "context" "database/sql" "errors" "fmt" "strings" "github.com/google/uuid" ) type Repository struct { db *sql.DB } func NewRepository(db *sql.DB) *Repository { return &Repository{db: db} } var ErrEmailTaken = errors.New("email already registered") func (r *Repository) CreateUser(ctx context.Context, email, passwordHash, name string) (User, error) { user := User{ID: uuid.New(), Email: strings.ToLower(strings.TrimSpace(email)), Name: strings.TrimSpace(name)} _, err := r.db.ExecContext(ctx, ` INSERT INTO users (id, email, password_hash, name) VALUES ($1, $2, $3, $4) `, user.ID, user.Email, passwordHash, user.Name) if err != nil { if strings.Contains(strings.ToLower(err.Error()), "unique") { return User{}, ErrEmailTaken } return User{}, fmt.Errorf("create user: %w", err) } return user, nil } func (r *Repository) FindByEmail(ctx context.Context, email string) (storedUser, error) { var user storedUser err := r.db.QueryRowContext(ctx, ` SELECT id, email, name, studio_name, tagline, website_url, instagram_url, password_hash FROM users WHERE lower(email) = lower($1) `, strings.TrimSpace(email)).Scan(&user.ID, &user.Email, &user.Name, &user.StudioName, &user.Tagline, &user.WebsiteURL, &user.InstagramURL, &user.PasswordHash) if errors.Is(err, sql.ErrNoRows) { return storedUser{}, sql.ErrNoRows } if err != nil { return storedUser{}, fmt.Errorf("find user by email: %w", err) } return user, nil } func (r *Repository) FindByID(ctx context.Context, id uuid.UUID) (User, error) { var user User err := r.db.QueryRowContext(ctx, ` SELECT id, email, name, studio_name, tagline, website_url, instagram_url FROM users WHERE id = $1 `, id).Scan(&user.ID, &user.Email, &user.Name, &user.StudioName, &user.Tagline, &user.WebsiteURL, &user.InstagramURL) if errors.Is(err, sql.ErrNoRows) { return User{}, sql.ErrNoRows } if err != nil { return User{}, fmt.Errorf("find user by id: %w", err) } return user, nil } func (r *Repository) FindByIDWithPassword(ctx context.Context, id uuid.UUID) (storedUser, error) { var user storedUser err := r.db.QueryRowContext(ctx, ` SELECT id, email, name, studio_name, tagline, website_url, instagram_url, password_hash FROM users WHERE id = $1 `, id).Scan(&user.ID, &user.Email, &user.Name, &user.StudioName, &user.Tagline, &user.WebsiteURL, &user.InstagramURL, &user.PasswordHash) if errors.Is(err, sql.ErrNoRows) { return storedUser{}, sql.ErrNoRows } if err != nil { return storedUser{}, fmt.Errorf("find user credentials: %w", err) } return user, nil } func (r *Repository) UpdateUser(ctx context.Context, id uuid.UUID, email, name, studioName, tagline, websiteURL, instagramURL string) (User, error) { _, err := r.db.ExecContext(ctx, ` UPDATE users SET email = $1, name = $2, studio_name = $3, tagline = $4, website_url = $5, instagram_url = $6, updated_at = CURRENT_TIMESTAMP WHERE id = $7 `, strings.ToLower(strings.TrimSpace(email)), strings.TrimSpace(name), studioName, tagline, websiteURL, instagramURL, id) if err != nil { if strings.Contains(strings.ToLower(err.Error()), "unique") { return User{}, ErrEmailTaken } return User{}, fmt.Errorf("update user: %w", err) } return r.FindByID(ctx, id) } func (r *Repository) UpdatePassword(ctx context.Context, id uuid.UUID, passwordHash string) error { result, err := r.db.ExecContext(ctx, ` UPDATE users SET password_hash = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 `, passwordHash, id) if err != nil { return fmt.Errorf("update password: %w", err) } count, err := result.RowsAffected() if err != nil || count == 0 { return sql.ErrNoRows } return nil }