package sqlite import ( "context" "database/sql" "fmt" "strings" "apocapoc-api/internal/domain/entities" "apocapoc-api/internal/shared/errors" "github.com/google/uuid" ) type UserRepository struct { db *sql.DB } func NewUserRepository(db *sql.DB) *UserRepository { return &UserRepository{db: db} } func (r *UserRepository) Create(ctx context.Context, user *entities.User) error { user.ID = uuid.New().String() query := ` INSERT INTO users (id, email, password_hash, timezone, email_verified, email_verification_token, email_verification_expiry, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ` _, err := r.db.ExecContext(ctx, query, user.ID, user.Email, user.PasswordHash, user.Timezone, user.EmailVerified, user.EmailVerificationToken, user.EmailVerificationExpiry, user.CreatedAt, user.UpdatedAt, ) if err != nil { if isUniqueConstraintError(err) { return errors.ErrAlreadyExists } return fmt.Errorf("failed to create user: %w", err) } return nil } func (r *UserRepository) FindByID(ctx context.Context, id string) (*entities.User, error) { query := ` SELECT id, email, password_hash, timezone, email_verified, email_verification_token, email_verification_expiry, created_at, updated_at FROM users WHERE id = ? ` var user entities.User err := r.db.QueryRowContext(ctx, query, id).Scan( &user.ID, &user.Email, &user.PasswordHash, &user.Timezone, &user.EmailVerified, &user.EmailVerificationToken, &user.EmailVerificationExpiry, &user.CreatedAt, &user.UpdatedAt, ) if err == sql.ErrNoRows { return nil, errors.ErrNotFound } if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } return &user, nil } func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*entities.User, error) { query := ` SELECT id, email, password_hash, timezone, email_verified, email_verification_token, email_verification_expiry, created_at, updated_at FROM users WHERE email = ? ` var user entities.User err := r.db.QueryRowContext(ctx, query, email).Scan( &user.ID, &user.Email, &user.PasswordHash, &user.Timezone, &user.EmailVerified, &user.EmailVerificationToken, &user.EmailVerificationExpiry, &user.CreatedAt, &user.UpdatedAt, ) if err == sql.ErrNoRows { return nil, errors.ErrNotFound } if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } return &user, nil } func (r *UserRepository) FindByVerificationToken(ctx context.Context, token string) (*entities.User, error) { query := ` SELECT id, email, password_hash, timezone, email_verified, email_verification_token, email_verification_expiry, created_at, updated_at FROM users WHERE email_verification_token = ? ` var user entities.User err := r.db.QueryRowContext(ctx, query, token).Scan( &user.ID, &user.Email, &user.PasswordHash, &user.Timezone, &user.EmailVerified, &user.EmailVerificationToken, &user.EmailVerificationExpiry, &user.CreatedAt, &user.UpdatedAt, ) if err == sql.ErrNoRows { return nil, errors.ErrNotFound } if err != nil { return nil, fmt.Errorf("failed to find user by verification token: %w", err) } return &user, nil } func (r *UserRepository) Update(ctx context.Context, user *entities.User) error { query := ` UPDATE users SET email = ?, password_hash = ?, timezone = ?, email_verified = ?, email_verification_token = ?, email_verification_expiry = ?, updated_at = ? WHERE id = ? ` result, err := r.db.ExecContext(ctx, query, user.Email, user.PasswordHash, user.Timezone, user.EmailVerified, user.EmailVerificationToken, user.EmailVerificationExpiry, user.UpdatedAt, user.ID, ) if err != nil { return fmt.Errorf("failed to update user: %w", err) } rows, _ := result.RowsAffected() if rows == 0 { return errors.ErrNotFound } return nil } func isUniqueConstraintError(err error) bool { return err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") }