package db import ( "context" "database/sql" "errors" "fmt" "strings" "time" ) type CreateUserParams struct { Email string PasswordHash string Role UserRole IsActive bool } func (r *UserRepository) HasActiveAdmin(ctx context.Context) (bool, error) { var exists int if err := r.q.QueryRowContext( ctx, `SELECT EXISTS(SELECT 1 FROM users WHERE role = ? AND is_active = 1 LIMIT 1)`, UserRoleAdmin, ).Scan(&exists); err != nil { return false, fmt.Errorf("query active admin existence: %w", err) } return exists == 1, nil } func (r *UserRepository) GetByID(ctx context.Context, id int64) (*User, error) { user, err := scanUser(r.q.QueryRowContext( ctx, `SELECT id, email, password_hash, role, is_active, created_at, updated_at, last_login_at FROM users WHERE id = ?`, id, )) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } return nil, fmt.Errorf("scan user by id: %w", err) } return user, nil } func (r *UserRepository) GetByEmail(ctx context.Context, email string) (*User, error) { user, err := scanUser(r.q.QueryRowContext( ctx, `SELECT id, email, password_hash, role, is_active, created_at, updated_at, last_login_at FROM users WHERE email = ? COLLATE NOCASE LIMIT 1`, strings.TrimSpace(email), )) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } return nil, fmt.Errorf("scan user by email: %w", err) } return user, nil } func (r *UserRepository) Create(ctx context.Context, params CreateUserParams) (*User, error) { isActive := 0 if params.IsActive { isActive = 1 } result, err := r.q.ExecContext( ctx, `INSERT INTO users (email, password_hash, role, is_active) VALUES (?, ?, ?, ?)`, strings.TrimSpace(params.Email), params.PasswordHash, params.Role, isActive, ) if err != nil { return nil, fmt.Errorf("insert user: %w", err) } userID, err := result.LastInsertId() if err != nil { return nil, fmt.Errorf("load inserted user id: %w", err) } return r.GetByID(ctx, userID) } func (r *UserRepository) UpdateLastLoginAt(ctx context.Context, userID int64, loggedInAt time.Time) error { result, err := r.q.ExecContext( ctx, `UPDATE users SET last_login_at = ? WHERE id = ?`, formatTimestamp(loggedInAt), userID, ) if err != nil { return fmt.Errorf("update user last_login_at: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("read affected user rows: %w", err) } if rowsAffected == 0 { return ErrNotFound } return nil } func scanUser(scanner rowScanner) (*User, error) { var ( user User role string isActive int createdAtRaw string updatedAtRaw string lastLoginRaw sql.NullString ) if err := scanner.Scan( &user.ID, &user.Email, &user.PasswordHash, &role, &isActive, &createdAtRaw, &updatedAtRaw, &lastLoginRaw, ); err != nil { return nil, err } createdAt, err := parseTimestamp(createdAtRaw) if err != nil { return nil, fmt.Errorf("parse user created_at: %w", err) } updatedAt, err := parseTimestamp(updatedAtRaw) if err != nil { return nil, fmt.Errorf("parse user updated_at: %w", err) } lastLoginAt, err := parseNullableTimestamp(lastLoginRaw) if err != nil { return nil, fmt.Errorf("parse user last_login_at: %w", err) } user.Role = UserRole(role) user.IsActive = isActive == 1 user.CreatedAt = createdAt user.UpdatedAt = updatedAt user.LastLoginAt = lastLoginAt return &user, nil }