Files
evening_detective_server/internal/repos/teams_repo/repo.go
T
2026-10-11 16:00:18 +07:00

309 lines
7.0 KiB
Go

package teams_repo
import (
"context"
"errors"
"evening_detective_server/internal/repos"
"github.com/google/uuid"
"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 string) (*repos.Team, error) {
if !validTeamID(id) {
return nil, ErrTeamNotFound
}
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,
) (string, error) {
return r.AddTeamWithParticipation(ctx, creatorID, gameID, name, password, "", 0, false)
}
// AddTeamWithParticipation creates a team with participation data.
func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorID, gameID int, name, password, captainEmail string, participantsCount int32, autoConfirm bool) (string, error) {
id, err := uuid.NewV7()
if err != nil {
return "", err
}
var teamID string
status, source := "pending", ""
if autoConfirm {
status, source = "confirmed", "automatic"
}
err = r.pool.QueryRow(
ctx,
`INSERT INTO teams (
id, name, creator_id, game_id, password, participation_status,
participation_source, participation_answered_at, captain_email, participants_count
)
VALUES (
$9, $1, $2, $3, $4, $5, $6,
CASE WHEN $5 = 'confirmed' THEN NOW() ELSE NULL END, $7, $8
)
RETURNING
id`,
name,
creatorID,
gameID,
password,
status,
source,
captainEmail,
participantsCount,
id.String(),
).Scan(&teamID)
if err != nil {
return "", err
}
return teamID, nil
}
// UpdateTeam changes a team's name.
func (r *TeamsRepo) UpdateTeam(ctx context.Context, teamID string, name string) error {
if !validTeamID(teamID) {
return ErrTeamNotFound
}
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 string) error {
if !validTeamID(teamID) {
return ErrTeamNotFound
}
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("игра не найдена или регистрация уже закрыта")
func (r *TeamsRepo) RegisterTeam(ctx context.Context, gameID int, deliveryID, name, email string, participantsCount int32, password string) (string, 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) (string, error) {
var id string
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 "", err
}
newID, err := uuid.NewV7()
if err != nil {
return "", err
}
status, source := "pending", ""
if autoConfirm {
status, source = "confirmed", "automatic"
}
err = r.pool.QueryRow(
ctx,
`INSERT INTO teams (
id, game_id, registration_delivery_id, name, captain_email,
participants_count, password, participation_status,
participation_source, participation_answered_at
)
SELECT
$9,
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,
newID.String(),
).Scan(&id)
if errors.Is(err, pgx.ErrNoRows) {
return "", ErrRegistrationClosed
}
return id, err
}
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
}
func validTeamID(id string) bool {
parsed, err := uuid.Parse(id)
return err == nil && parsed != uuid.Nil && parsed.String() == id
}