update_server/internal/db/users.go
2026-06-10 20:51:17 +03:00

165 lines
3.4 KiB
Go

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
}