Files
evening_detective_server/internal/repos/teams_repo/repo.go
T
2026-10-01 13:13:03 +07:00

226 lines
5.7 KiB
Go

package teams_repo
import (
"context"
"errors"
"evening_detective_server/internal/repos"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
ErrTeamNotFound = errors.New("Команда не найдена")
)
type TeamsRepo struct {
pool *pgxpool.Pool
}
func NewTeamsRepo(pool *pgxpool.Pool) *TeamsRepo {
return &TeamsRepo{
pool: pool,
}
}
func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error) {
team := &repos.Team{}
row := r.pool.QueryRow(
ctx,
`SELECT
id,
name,
captain_email,
participants_count,
participation_status, participation_source, participation_email_error, participation_sent_at IS NOT NULL,
status,
password,
game_id
FROM teams
WHERE id = $1`,
id,
)
err := row.Scan(
&team.ID,
&team.Name,
&team.CaptainEmail,
&team.ParticipantsCount,
&team.ParticipationStatus, &team.ParticipationSource, &team.ParticipationEmailError, &team.ParticipationEmailSent,
&team.Status,
&team.Password,
&team.GameID,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrTeamNotFound
}
return nil, err
}
return team, nil
}
func (r *TeamsRepo) GetTeamsByGameID(
ctx context.Context,
gameID int,
) ([]*repos.Team, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
id,
name,
captain_email,
participants_count,
participation_status, participation_source, participation_email_error, participation_sent_at IS NOT NULL,
status,
password
FROM teams
WHERE game_id = $1
ORDER BY created_at DESC`,
gameID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var teams []*repos.Team
for rows.Next() {
team := &repos.Team{}
err := rows.Scan(
&team.ID,
&team.Name,
&team.CaptainEmail,
&team.ParticipantsCount,
&team.ParticipationStatus, &team.ParticipationSource, &team.ParticipationEmailError, &team.ParticipationEmailSent,
&team.Status,
&team.Password,
)
if err != nil {
return nil, err
}
teams = append(teams, team)
}
if err := rows.Err(); err != nil {
return nil, err
}
return teams, nil
}
func (r *TeamsRepo) AddTeam(
ctx context.Context,
creatorID int,
gameID int,
name string,
password string,
) (int, error) {
return r.AddTeamWithParticipation(ctx, creatorID, gameID, name, password, "", false)
}
func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorID, gameID int, name, password, captainEmail string, autoConfirm bool) (int, error) {
id := 0
status, source := "pending", ""
if autoConfirm {
status, source = "confirmed", "automatic"
}
err := r.pool.QueryRow(
ctx,
`INSERT INTO teams (name, creator_id, game_id, password, participation_status, participation_source, participation_answered_at, captain_email)
VALUES ($1, $2, $3, $4, $5, $6, CASE WHEN $5 = 'confirmed' THEN NOW() ELSE NULL END, $7)
RETURNING id`,
name,
creatorID,
gameID,
password,
status,
source,
captainEmail,
).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
func (r *TeamsRepo) UpdateTeam(ctx context.Context, teamID int, name string) error {
tag, err := r.pool.Exec(
ctx,
`UPDATE teams
SET
name = $1
WHERE id = $2`,
name,
teamID,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrTeamNotFound
}
return nil
}
func (r *TeamsRepo) DeleteTeam(ctx context.Context, teamID int) error {
tag, err := r.pool.Exec(
ctx,
`DELETE FROM teams
WHERE id = $1`,
teamID,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrTeamNotFound
}
return nil
}
var ErrRegistrationClosed = errors.New("Игра не найдена или регистрация уже закрыта")
// RegisterTeam сохраняет одну команду на доставку, включая одновременные повторы.
func (r *TeamsRepo) RegisterTeam(ctx context.Context, gameID int, deliveryID, name, email string, participantsCount int32, password string) (int, error) {
return r.RegisterTeamWithParticipation(ctx, gameID, deliveryID, name, email, participantsCount, password, false)
}
func (r *TeamsRepo) RegisterTeamWithParticipation(ctx context.Context, gameID int, deliveryID, name, email string, participantsCount int32, password string, autoConfirm bool) (int, error) {
var id int
// Повтор уже принятой доставки успешен и после старта игры.
err := r.pool.QueryRow(ctx, `SELECT id FROM teams WHERE game_id = $1 AND registration_delivery_id = $2`, gameID, deliveryID).Scan(&id)
if err == nil {
return id, nil
}
if !errors.Is(err, pgx.ErrNoRows) {
return 0, err
}
status, source := "pending", ""
if autoConfirm {
status, source = "confirmed", "automatic"
}
err = r.pool.QueryRow(ctx, `
INSERT INTO teams (game_id, registration_delivery_id, name, captain_email, participants_count, password, participation_status, participation_source, participation_answered_at)
SELECT id, $2, $3, $4, $5, $6, $7, $8, CASE WHEN $7 = 'confirmed' THEN NOW() ELSE NULL END FROM games WHERE id = $1 AND status = 'draft'
ON CONFLICT (game_id, registration_delivery_id)
DO UPDATE SET registration_delivery_id = EXCLUDED.registration_delivery_id
RETURNING id`, gameID, deliveryID, name, email, participantsCount, password, status, source).Scan(&id)
if errors.Is(err, pgx.ErrNoRows) {
return 0, ErrRegistrationClosed
}
return id, err
}
// GetActiveTeamsByGameID excludes cancelled registrations from gameplay exports and results.
func (r *TeamsRepo) GetActiveTeamsByGameID(ctx context.Context, gameID int) ([]*repos.Team, error) {
teams, err := r.GetTeamsByGameID(ctx, gameID)
active := teams[:0]
for _, team := range teams {
if team.ParticipationStatus != "cancelled" {
active = append(active, team)
}
}
return active, err
}