generated from VLADIMIR/template
237 lines
6.3 KiB
Go
237 lines
6.3 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 indicates that a team was not found.
|
|
ErrTeamNotFound = errors.New("Команда не найдена")
|
|
)
|
|
|
|
// TeamsRepo persists teams and their game membership.
|
|
type TeamsRepo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// NewTeamsRepo creates a teams repository.
|
|
func NewTeamsRepo(pool *pgxpool.Pool) *TeamsRepo {
|
|
return &TeamsRepo{
|
|
pool: pool,
|
|
}
|
|
}
|
|
|
|
// GetTeamByID returns a team by ID.
|
|
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
|
|
}
|
|
|
|
// GetTeamsByGameID returns teams registered for a game.
|
|
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
|
|
}
|
|
|
|
// AddTeam creates a team.
|
|
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)
|
|
}
|
|
|
|
// AddTeamWithParticipation creates a team with participation data.
|
|
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
|
|
}
|
|
|
|
// UpdateTeam changes a team's name.
|
|
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
|
|
}
|
|
|
|
// DeleteTeam removes a team.
|
|
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
|
|
}
|
|
|
|
// ErrRegistrationClosed indicates that team registration is unavailable.
|
|
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)
|
|
}
|
|
|
|
// RegisterTeamWithParticipation registers a team and its participation data.
|
|
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 исключает отменённые регистрации из игровых выгрузок и результатов.
|
|
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
|
|
}
|