Files
evening_detective_server/internal/repos/tickets_repo/repo.go
T
2026-10-03 13:33:18 +07:00

118 lines
3.1 KiB
Go

package tickets_repo
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
// ErrTicketNotFound indicates that a ticket was not found.
ErrTicketNotFound = errors.New("билет не найден")
// ErrTicketUsed indicates that a ticket was already used.
ErrTicketUsed = errors.New("билет уже использован")
// ErrGameFinished indicates that the game has finished.
ErrGameFinished = errors.New("игра уже завершена")
)
// Info describes a support ticket.
type Info struct {
ID int
Status string
GameName string
StartAt time.Time
Location *string
GameStatus string
UsedAt *time.Time
UsedBy *string
}
// Repo persists support tickets.
type Repo struct {
pool *pgxpool.Pool
}
// New creates a tickets repository.
func New(pool *pgxpool.Pool) *Repo {
return &Repo{pool: pool}
}
// CreateBatch creates tickets for a game.
func (r *Repo) CreateBatch(ctx context.Context, gameID int, tokenHashes [][]byte) ([]int, error) {
tx, err := r.pool.Begin(ctx)
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback(ctx) }()
ids := make([]int, 0, len(tokenHashes))
for _, tokenHash := range tokenHashes {
var id int
if err := tx.QueryRow(ctx,
`INSERT INTO tickets (game_id, token_hash) VALUES ($1, $2) RETURNING id`,
gameID, tokenHash,
).Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return ids, nil
}
// GetByHash returns a ticket by token hash.
func (r *Repo) GetByHash(ctx context.Context, tokenHash []byte) (*Info, error) {
info := &Info{}
err := r.pool.QueryRow(ctx, `
SELECT t.id, t.status, s.name, g.start_at, g.location, g.status, t.used_at, u.username
FROM tickets t
JOIN games g ON g.id = t.game_id
JOIN scenarios s ON s.id = g.scenario_id
LEFT JOIN users u ON u.id = t.used_by
WHERE t.token_hash = $1`, tokenHash).Scan(
&info.ID, &info.Status, &info.GameName, &info.StartAt, &info.Location, &info.GameStatus, &info.UsedAt, &info.UsedBy)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrTicketNotFound
}
return info, err
}
// Accept assigns a ticket to a user.
func (r *Repo) Accept(ctx context.Context, tokenHash []byte, userID int) (*Info, error) {
var status, gameStatus string
if err := r.pool.QueryRow(ctx, `
SELECT t.status, g.status
FROM tickets t JOIN games g ON g.id = t.game_id
WHERE t.token_hash = $1`, tokenHash).Scan(&status, &gameStatus); errors.Is(err, pgx.ErrNoRows) {
return nil, ErrTicketNotFound
} else if err != nil {
return nil, err
}
if status == "used" {
return nil, ErrTicketUsed
}
if gameStatus == "finish" {
return nil, ErrGameFinished
}
tag, err := r.pool.Exec(ctx, `
UPDATE tickets t SET status = 'used', used_at = NOW(), used_by = $2
FROM games g
WHERE t.game_id = g.id AND t.token_hash = $1 AND t.status = 'active' AND g.status <> 'finish'`, tokenHash, userID)
if err != nil {
return nil, err
}
if tag.RowsAffected() == 0 {
if status == "used" {
return nil, ErrTicketUsed
}
return nil, ErrGameFinished
}
return r.GetByHash(ctx, tokenHash)
}