97 lines
3.0 KiB
Go
97 lines
3.0 KiB
Go
// File: backend/internal/database/user_repository.go
|
||
package database
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"errors"
|
||
|
||
"gitea.mrixs.me/minecraft-platform/backend/internal/models"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
var (
|
||
ErrUserExists = errors.New("user with this username or email already exists")
|
||
)
|
||
|
||
type UserRepository struct {
|
||
DB *sql.DB
|
||
}
|
||
|
||
// CreateUserTx создает нового пользователя и его профиль в рамках одной транзакции
|
||
func (r *UserRepository) CreateUserTx(ctx context.Context, user *models.User) error {
|
||
tx, err := r.DB.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// Гарантируем откат транзакции в случае любой ошибки
|
||
defer tx.Rollback()
|
||
|
||
// Шаг 4 из ТЗ: Проверка уникальности
|
||
var exists bool
|
||
err = tx.QueryRowContext(ctx,
|
||
"SELECT EXISTS(SELECT 1 FROM users WHERE username = $1 OR email = $2)",
|
||
user.Username, user.Email).Scan(&exists)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if exists {
|
||
return ErrUserExists
|
||
}
|
||
|
||
// Шаг 7 из ТЗ: INSERT в таблицу users. Получаем ID нового пользователя.
|
||
var newUserID int
|
||
err = tx.QueryRowContext(ctx,
|
||
"INSERT INTO users (uuid, username, email, password_hash, role) VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
||
user.UUID, user.Username, user.Email, user.PasswordHash, user.Role,
|
||
).Scan(&newUserID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Шаг 9 из ТЗ: INSERT в таблицу profiles
|
||
_, err = tx.ExecContext(ctx, "INSERT INTO profiles (user_id) VALUES ($1)", newUserID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Шаг 10 из ТЗ: Коммитим транзакцию
|
||
return tx.Commit()
|
||
}
|
||
|
||
var (
|
||
ErrUserExists = errors.New("user with this username or email already exists")
|
||
ErrUserNotFound = errors.New("user not found") // Новая ошибка
|
||
)
|
||
|
||
// ...
|
||
|
||
// GetUserByUsername находит пользователя по его имени.
|
||
// Возвращает полную структуру User, включая хеш пароля для проверки.
|
||
func (r *UserRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
|
||
user := &models.User{}
|
||
var userUUID string
|
||
|
||
query := "SELECT id, uuid, username, email, password_hash, role FROM users WHERE username = $1"
|
||
err := r.DB.QueryRowContext(ctx, query, username).Scan(
|
||
&user.ID, &userUUID, &user.Username, &user.Email, &user.PasswordHash, &user.Role,
|
||
)
|
||
|
||
if err != nil {
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
return nil, ErrUserNotFound
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
user.UUID, _ = uuid.Parse(userUUID)
|
||
return user, nil
|
||
}
|
||
|
||
// CreateAccessToken сохраняет новый токен доступа в базу данных.
|
||
func (r *UserRepository) CreateAccessToken(ctx context.Context, userID int, accessToken, clientToken string) error {
|
||
query := "INSERT INTO access_tokens (user_id, access_token, client_token) VALUES ($1, $2, $3)"
|
||
_, err := r.DB.ExecContext(ctx, query, userID, accessToken, clientToken)
|
||
return err
|
||
}
|