diff --git a/internal/repos/actions_repo/repo.go b/internal/repos/actions_repo/repo.go index 8448e31..4b45088 100644 --- a/internal/repos/actions_repo/repo.go +++ b/internal/repos/actions_repo/repo.go @@ -9,24 +9,29 @@ import ( ) var ( + // ErrActionsNotFound indicates that no actions were found. ErrActionsNotFound = errors.New("Ход не найден") ) +// ActionsRepo persists team actions. type ActionsRepo struct { pool *pgxpool.Pool } +// Action describes a team action. type Action struct { Code string ViaMCP bool } +// NewActionsRepo creates an actions repository. func NewActionsRepo(pool *pgxpool.Pool) *ActionsRepo { return &ActionsRepo{ pool: pool, } } +// AddAction stores an action for a team. func (r *ActionsRepo) AddAction(ctx context.Context, teamID int, code string, viaMCP bool) (int, error) { id := 0 err := r.pool.QueryRow( @@ -45,6 +50,7 @@ func (r *ActionsRepo) AddAction(ctx context.Context, teamID int, code string, vi return id, nil } +// GetActionsByTeamID returns actions for a team. func (r *ActionsRepo) GetActionsByTeamID(ctx context.Context, teamID int) ([]Action, error) { rows, err := r.pool.Query( ctx, @@ -80,6 +86,7 @@ func (r *ActionsRepo) GetActionsByTeamID(ctx context.Context, teamID int) ([]Act return actions, nil } +// GetActionsCountByTeamIDs returns action counts for teams. func (r *ActionsRepo) GetActionsCountByTeamIDs(ctx context.Context, teamIDs []int) (map[int]int, error) { rows, err := r.pool.Query( ctx, @@ -116,6 +123,7 @@ func (r *ActionsRepo) GetActionsCountByTeamIDs(ctx context.Context, teamIDs []in return res, nil } +// DeleteLastActionByTeamID removes the latest action for a team. func (r *ActionsRepo) DeleteLastActionByTeamID(ctx context.Context, teamID int) error { tag, err := r.pool.Exec( ctx, diff --git a/internal/repos/applications_repo/repo.go b/internal/repos/applications_repo/repo.go index 80cd4b7..786031f 100644 --- a/internal/repos/applications_repo/repo.go +++ b/internal/repos/applications_repo/repo.go @@ -10,19 +10,23 @@ import ( ) var ( + // ErrApplicationNotFound indicates that an application was not found. ErrApplicationNotFound = errors.New("Улика не найдена") ) +// ApplicationsRepo persists team applications. type ApplicationsRepo struct { pool *pgxpool.Pool } +// NewApplicationsRepo creates an applications repository. func NewApplicationsRepo(pool *pgxpool.Pool) *ApplicationsRepo { return &ApplicationsRepo{ pool: pool, } } +// AddApplication stores an application for a team. func (r *ApplicationsRepo) AddApplication( ctx context.Context, teamID int, @@ -48,6 +52,7 @@ func (r *ApplicationsRepo) AddApplication( return id, nil } +// GetApplicationsByTeamIDsAndState returns applications matching team IDs and state. func (r *ApplicationsRepo) GetApplicationsByTeamIDsAndState( ctx context.Context, teamIDs []int, @@ -91,6 +96,7 @@ func (r *ApplicationsRepo) GetApplicationsByTeamIDsAndState( return res, nil } +// UpdateApplicationStatus changes an application's status. func (r *ApplicationsRepo) UpdateApplicationStatus( ctx context.Context, teamID int, @@ -116,6 +122,7 @@ func (r *ApplicationsRepo) UpdateApplicationStatus( return nil } +// DeleteApplicationByTeamIDAndName removes an application by team and name. func (r *ApplicationsRepo) DeleteApplicationByTeamIDAndName(ctx context.Context, teamID int, name string) error { tag, err := r.pool.Exec( ctx, diff --git a/internal/repos/game_reviews_repo/repo.go b/internal/repos/game_reviews_repo/repo.go index d11e287..8495eb3 100644 --- a/internal/repos/game_reviews_repo/repo.go +++ b/internal/repos/game_reviews_repo/repo.go @@ -11,12 +11,17 @@ import ( ) var ( + // ErrReviewNotFound indicates that a review was not found. ErrReviewNotFound = errors.New("Отзыв не найден") - ErrReviewExists = errors.New("Отзыв от команды уже оставлен") - ErrTeamNotFound = errors.New("Команда не найдена") - ErrInvalidStatus = errors.New("Недопустимый статус модерации") + // ErrReviewExists indicates that a team already submitted a review. + ErrReviewExists = errors.New("Отзыв от команды уже оставлен") + // ErrTeamNotFound indicates that a team was not found. + ErrTeamNotFound = errors.New("Команда не найдена") + // ErrInvalidStatus indicates that a moderation status is invalid. + ErrInvalidStatus = errors.New("Недопустимый статус модерации") ) +// Review describes a game review. type Review struct { ID int GameID int @@ -29,10 +34,13 @@ type Review struct { CreatedAt time.Time } +// Repo persists game reviews. type Repo struct{ pool *pgxpool.Pool } +// NewGameReviewsRepo creates a game reviews repository. func NewGameReviewsRepo(pool *pgxpool.Pool) *Repo { return &Repo{pool: pool} } +// Create stores a review. func (r *Repo) Create(ctx context.Context, gameID, teamID int, rating int32, text string) (*Review, error) { review := &Review{} err := r.pool.QueryRow( @@ -72,10 +80,12 @@ func (r *Repo) Create(ctx context.Context, gameID, teamID int, rating int32, tex return review, err } +// GetByID returns a review by ID. func (r *Repo) GetByID(ctx context.Context, id int) (*Review, error) { return r.queryOne(ctx, id) } +// List returns reviews for a game and aggregate rating data. func (r *Repo) List(ctx context.Context, gameID int, all bool) ([]*Review, float64, int32, error) { rows, err := r.pool.Query( ctx, @@ -117,6 +127,7 @@ func (r *Repo) List(ctx context.Context, gameID int, all bool) ([]*Review, float return reviews, average, int32(count), nil } +// ListForModeration returns reviews awaiting moderation. func (r *Repo) ListForModeration(ctx context.Context) ([]*Review, error) { rows, err := r.pool.Query( ctx, @@ -141,6 +152,7 @@ func (r *Repo) ListForModeration(ctx context.Context) ([]*Review, error) { return reviews, rows.Err() } +// Moderate updates a review moderation status. func (r *Repo) Moderate(ctx context.Context, id int, status string) (*Review, error) { if status != "approved" && status != "rejected" && status != "hidden" { return nil, ErrInvalidStatus @@ -162,6 +174,7 @@ func (r *Repo) Moderate(ctx context.Context, id int, status string) (*Review, er return r.queryOne(ctx, id) } +// ScenarioRating returns aggregate ratings for a scenario. func (r *Repo) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, error) { var average float64 var count int64 @@ -176,6 +189,7 @@ func (r *Repo) ScenarioRating(ctx context.Context, scenarioID int) (float64, int return average, int32(count), err } +// ListByScenario returns reviews and aggregate ratings for a scenario. func (r *Repo) ListByScenario(ctx context.Context, scenarioID int) ([]*Review, float64, int32, error) { rows, err := r.pool.Query( ctx, diff --git a/internal/repos/games_repo/repo.go b/internal/repos/games_repo/repo.go index f5ca639..33b089e 100644 --- a/internal/repos/games_repo/repo.go +++ b/internal/repos/games_repo/repo.go @@ -12,19 +12,23 @@ import ( ) var ( + // ErrGameNotFound indicates that a game was not found. ErrGameNotFound = errors.New("Игра не найдена") ) +// GamesRepo persists games. type GamesRepo struct { pool *pgxpool.Pool } +// NewGamesRepo creates a games repository. func NewGamesRepo(pool *pgxpool.Pool) *GamesRepo { return &GamesRepo{ pool: pool, } } +// AddGame stores a game. func (r *GamesRepo) AddGame( ctx context.Context, name string, @@ -54,6 +58,7 @@ func (r *GamesRepo) AddGame( return id, nil } +// GetGameByID returns a game by ID. func (r *GamesRepo) GetGameByID( ctx context.Context, id int, @@ -98,6 +103,7 @@ func (r *GamesRepo) GetGameByID( return game, nil } +// DeleteGameByID removes a game by ID. func (r *GamesRepo) DeleteGameByID( ctx context.Context, id int, @@ -117,6 +123,7 @@ func (r *GamesRepo) DeleteGameByID( return nil } +// GetGames returns games matching the requested filters. func (r *GamesRepo) GetGames( ctx context.Context, ) ([]*repos.Game, error) { @@ -162,6 +169,7 @@ func (r *GamesRepo) GetGames( return games, nil } +// UpdateGame updates a game's editable fields. func (r *GamesRepo) UpdateGame( ctx context.Context, id int, @@ -201,6 +209,7 @@ func (r *GamesRepo) UpdateGame( return nil } +// StartGame marks a game as started. func (r *GamesRepo) StartGame(ctx context.Context, id int) error { tag, err := r.pool.Exec( ctx, @@ -221,6 +230,7 @@ func (r *GamesRepo) StartGame(ctx context.Context, id int) error { return nil } +// PauseGame marks a game as paused. func (r *GamesRepo) PauseGame(ctx context.Context, id int) error { tag, err := r.pool.Exec( ctx, @@ -240,6 +250,7 @@ func (r *GamesRepo) PauseGame(ctx context.Context, id int) error { return nil } +// PlayGame resumes a paused game. func (r *GamesRepo) PlayGame(ctx context.Context, id int) error { tag, err := r.pool.Exec( ctx, @@ -259,6 +270,7 @@ func (r *GamesRepo) PlayGame(ctx context.Context, id int) error { return nil } +// FinishGame marks a game as finished. func (r *GamesRepo) FinishGame(ctx context.Context, id int) error { tag, err := r.pool.Exec( ctx, @@ -279,6 +291,7 @@ func (r *GamesRepo) FinishGame(ctx context.Context, id int) error { return nil } +// ResetGame resets a game's state. func (r *GamesRepo) ResetGame(ctx context.Context, id int) error { tag, err := r.pool.Exec( ctx, diff --git a/internal/repos/screen_sessions_repo/repo.go b/internal/repos/screen_sessions_repo/repo.go index 77498ed..a939157 100644 --- a/internal/repos/screen_sessions_repo/repo.go +++ b/internal/repos/screen_sessions_repo/repo.go @@ -10,8 +10,10 @@ import ( "github.com/jackc/pgx/v5/pgxpool" ) +// ErrScreenNotFound indicates that a screen session was not found. var ErrScreenNotFound = errors.New("Экран не найден") +// ScreenSession describes a screen session. type ScreenSession struct { ID int GameID *int @@ -29,14 +31,17 @@ type ScreenSession struct { TimerEndsAt *time.Time } +// ScreenSessionsRepo persists screen sessions. type ScreenSessionsRepo struct { pool *pgxpool.Pool } +// NewScreenSessionsRepo creates a screen sessions repository. func NewScreenSessionsRepo(pool *pgxpool.Pool) *ScreenSessionsRepo { return &ScreenSessionsRepo{pool: pool} } +// Create creates a screen session. func (r *ScreenSessionsRepo) Create(ctx context.Context, tokenHash string) (int, error) { var id int err := r.pool.QueryRow(ctx, ` @@ -46,6 +51,7 @@ func (r *ScreenSessionsRepo) Create(ctx context.Context, tokenHash string) (int, return id, err } +// Get returns a screen session by ID and token hash. func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string) (*ScreenSession, error) { session := &ScreenSession{ID: id} err := r.pool.QueryRow(ctx, ` @@ -69,6 +75,7 @@ func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string) return session, nil } +// ListByGame returns screen sessions for a game. func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*ScreenSession, error) { rows, err := r.pool.Query(ctx, ` SELECT id, game_id, sound_enabled, last_seen_at, mode, state_version, position_ms, command, volume, @@ -93,21 +100,25 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr return sessions, rows.Err() } +// SetMode updates the display mode for a game. func (r *ScreenSessionsRepo) SetMode(ctx context.Context, gameID int, mode int32) error { _, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET mode = $2, command = 0, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, mode) return err } +// SetPosition updates the display position for a game. func (r *ScreenSessionsRepo) SetPosition(ctx context.Context, gameID int, position int64) error { _, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET position_ms = $2, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, position) return err } +// SetCommand updates the display command for a game. func (r *ScreenSessionsRepo) SetCommand(ctx context.Context, gameID int, command int32, position int64, volume int32) error { _, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET command = $2, position_ms = $3, volume = $4, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, command, position, volume) return err } +// SetTimer updates the display timer for a game. func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration int64, label string) error { _, err := r.pool.Exec(ctx, ` UPDATE screen_sessions @@ -124,6 +135,7 @@ func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration return err } +// SetTimerCommand updates the timer command for a game. func (r *ScreenSessionsRepo) SetTimerCommand(ctx context.Context, gameID int, command int32) error { _, err := r.pool.Exec(ctx, ` UPDATE screen_sessions @@ -154,6 +166,7 @@ func (r *ScreenSessionsRepo) SetTimerCommand(ctx context.Context, gameID int, co return err } +// Bind attaches a screen session to a game. func (r *ScreenSessionsRepo) Bind(ctx context.Context, id, gameID int, tokenHash string) error { result, err := r.pool.Exec(ctx, ` UPDATE screen_sessions @@ -168,6 +181,7 @@ func (r *ScreenSessionsRepo) Bind(ctx context.Context, id, gameID int, tokenHash return nil } +// Unbind detaches a screen session from its game. func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error { result, err := r.pool.Exec(ctx, ` UPDATE screen_sessions @@ -192,6 +206,7 @@ func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error { return nil } +// SetSound enables or disables sound for a screen session. func (r *ScreenSessionsRepo) SetSound(ctx context.Context, id int, enabled bool) error { query := `UPDATE screen_sessions SET sound_enabled = FALSE, updated_at = NOW() WHERE id = $1` args := []any{id} diff --git a/internal/repos/teams_repo/participation.go b/internal/repos/teams_repo/participation.go index e123820..d8ebe4f 100644 --- a/internal/repos/teams_repo/participation.go +++ b/internal/repos/teams_repo/participation.go @@ -11,14 +11,20 @@ import ( "github.com/jackc/pgx/v5" ) +// ErrParticipationLink indicates that a participation link is invalid. var ErrParticipationLink = errors.New("Ссылка недействительна") + +// ErrParticipationClosed indicates that participation answers are closed. var ErrParticipationClosed = errors.New("Приём ответов закрыт") + +// ErrParticipationFinal indicates that a final answer cannot be changed. var ErrParticipationFinal = errors.New("Решение уже принято и не может быть изменено") const maxParticipationEmailAttempts = 8 const maxReviewReminderAttempts = 8 +// Participation describes a team's participation state. type Participation struct { TeamID int TeamName string @@ -31,6 +37,7 @@ type Participation struct { Attempts int } +// ReviewReminder describes a pending review reminder. type ReviewReminder struct { TeamID int TeamName string @@ -53,6 +60,7 @@ func tokenHash(token string) ([]byte, error) { return hash[:], nil } +// GetParticipation returns participation data by token. func (r *TeamsRepo) GetParticipation(ctx context.Context, token string) (*Participation, error) { hash, err := tokenHash(token) if err != nil { @@ -69,6 +77,7 @@ func (r *TeamsRepo) GetParticipation(ctx context.Context, token string) (*Partic return p, err } +// AnswerParticipation stores a team's final answer. func (r *TeamsRepo) AnswerParticipation(ctx context.Context, token, answer string) (*Participation, error) { if answer != "confirmed" && answer != "cancelled" { return nil, errors.New("Выберите подтверждение или отмену участия") @@ -102,6 +111,7 @@ func (r *TeamsRepo) AnswerParticipation(ctx context.Context, token, answer strin // Пятиминутная аренда переживает перезапуски и не позволяет рабочим процессам // отправить одну задачу повторно. +// ClaimParticipationEmail claims the next participation email job. func (r *TeamsRepo) ClaimParticipationEmail(ctx context.Context) (*Participation, error) { p := &Participation{} err := r.pool.QueryRow(ctx, `WITH candidate AS ( @@ -122,6 +132,7 @@ func (r *TeamsRepo) ClaimParticipationEmail(ctx context.Context) (*Participation return p, err } +// SetParticipationToken stores a participation token. func (r *TeamsRepo) SetParticipationToken(ctx context.Context, id int, token string) error { hash, err := tokenHash(token) if err != nil { @@ -131,6 +142,7 @@ func (r *TeamsRepo) SetParticipationToken(ctx context.Context, id int, token str return err } +// ConfirmParticipationByFirstAction confirms participation after the first action. func (r *TeamsRepo) ConfirmParticipationByFirstAction(ctx context.Context, teamID int) error { _, err := r.pool.Exec(ctx, `UPDATE teams SET participation_status='confirmed', participation_source='first_action', participation_answered_at=NOW() @@ -138,6 +150,7 @@ func (r *TeamsRepo) ConfirmParticipationByFirstAction(ctx context.Context, teamI return err } +// FinishParticipationEmail completes a participation email job. func (r *TeamsRepo) FinishParticipationEmail(ctx context.Context, id, attempt int, sendErr error) error { message := "" if sendErr != nil { @@ -149,6 +162,7 @@ func (r *TeamsRepo) FinishParticipationEmail(ctx context.Context, id, attempt in return err } +// ClaimReviewReminder claims the next review reminder job. func (r *TeamsRepo) ClaimReviewReminder(ctx context.Context) (*ReviewReminder, error) { job := &ReviewReminder{} err := r.pool.QueryRow(ctx, `WITH candidate AS ( @@ -168,6 +182,7 @@ func (r *TeamsRepo) ClaimReviewReminder(ctx context.Context) (*ReviewReminder, e return job, err } +// FinishReviewReminder completes a review reminder job. func (r *TeamsRepo) FinishReviewReminder(ctx context.Context, id, attempt int, sendErr error) error { message := "" if sendErr != nil { diff --git a/internal/repos/teams_repo/repo.go b/internal/repos/teams_repo/repo.go index cb4a29c..68c27fe 100644 --- a/internal/repos/teams_repo/repo.go +++ b/internal/repos/teams_repo/repo.go @@ -10,19 +10,23 @@ import ( ) 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( @@ -60,6 +64,7 @@ func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error return team, nil } +// GetTeamsByGameID returns teams registered for a game. func (r *TeamsRepo) GetTeamsByGameID( ctx context.Context, gameID int, @@ -108,6 +113,7 @@ func (r *TeamsRepo) GetTeamsByGameID( return teams, nil } +// AddTeam creates a team. func (r *TeamsRepo) AddTeam( ctx context.Context, creatorID int, @@ -118,6 +124,7 @@ func (r *TeamsRepo) AddTeam( 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", "" @@ -144,6 +151,7 @@ func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorID, gam 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, @@ -163,6 +171,7 @@ func (r *TeamsRepo) UpdateTeam(ctx context.Context, teamID int, name string) err return nil } +// DeleteTeam removes a team. func (r *TeamsRepo) DeleteTeam(ctx context.Context, teamID int) error { tag, err := r.pool.Exec( ctx, @@ -179,6 +188,7 @@ func (r *TeamsRepo) DeleteTeam(ctx context.Context, teamID int) error { return nil } +// ErrRegistrationClosed indicates that team registration is unavailable. var ErrRegistrationClosed = errors.New("Игра не найдена или регистрация уже закрыта") // RegisterTeam сохраняет одну команду на доставку, включая одновременные повторы. @@ -186,6 +196,7 @@ func (r *TeamsRepo) RegisterTeam(ctx context.Context, gameID int, deliveryID, na 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 // Повтор уже принятой доставки успешен и после старта игры. diff --git a/internal/repos/users_repo/repo.go b/internal/repos/users_repo/repo.go index bcef54e..fd4af8b 100644 --- a/internal/repos/users_repo/repo.go +++ b/internal/repos/users_repo/repo.go @@ -14,13 +14,16 @@ import ( ) var ( + // ErrUserNotFound indicates that a user was not found. ErrUserNotFound = errors.New("Пользователь не найден") ) +// UsersRepo persists users and their roles. type UsersRepo struct { pool *pgxpool.Pool } +// NewUserRepo creates a users repository. func NewUserRepo(pool *pgxpool.Pool) *UsersRepo { return &UsersRepo{ pool: pool, @@ -105,6 +108,7 @@ func (s *UsersRepo) AddUserWithAgreements( return id, nil } +// UpdateUserPassword changes a user's password hash. func (s *UsersRepo) UpdateUserPassword( ctx context.Context, email string, @@ -128,6 +132,7 @@ func (s *UsersRepo) UpdateUserPassword( return nil } +// GetUserByEmail returns a user by email. func (s *UsersRepo) GetUserByEmail( ctx context.Context, email string, @@ -158,6 +163,7 @@ func (s *UsersRepo) GetUserByEmail( return user, nil } +// GetUserByID returns a user by ID. func (s *UsersRepo) GetUserByID( ctx context.Context, id int, @@ -188,6 +194,7 @@ func (s *UsersRepo) GetUserByID( return user, nil } +// GetUsers returns users matching the requested filters. func (s *UsersRepo) GetUsers( ctx context.Context, ) ([]*repos.User, error) { @@ -225,6 +232,7 @@ func (s *UsersRepo) GetUsers( return users, nil } +// AddUserRole assigns a role to a user. func (s *UsersRepo) AddUserRole( ctx context.Context, userID int, @@ -241,6 +249,7 @@ func (s *UsersRepo) AddUserRole( return err } +// DeleteUserRole removes a role from a user. func (s *UsersRepo) DeleteUserRole( ctx context.Context, userID int, diff --git a/internal/services/answer_service/service.go b/internal/services/answer_service/service.go index 838416d..0bb671b 100644 --- a/internal/services/answer_service/service.go +++ b/internal/services/answer_service/service.go @@ -13,6 +13,7 @@ import ( "evening_detective_server/internal/services/scenarios_service" ) +// AnswerService handles game answers. type AnswerService struct { gamesRepo *games_repo.GamesRepo teamsRepo *teams_repo.TeamsRepo @@ -21,6 +22,7 @@ type AnswerService struct { actionsRepo *actions_repo.ActionsRepo } +// NewAnswerService creates an answer service. func NewAnswerService( gamesRepo *games_repo.GamesRepo, teamsRepo *teams_repo.TeamsRepo, diff --git a/internal/services/file_service/service.go b/internal/services/file_service/service.go index 7a2502b..19de7ba 100644 --- a/internal/services/file_service/service.go +++ b/internal/services/file_service/service.go @@ -41,11 +41,13 @@ var allowedExtensions = map[string]struct{}{ ".webm": {}, } +// FileService handles file downloads. type FileService struct { fileStorage file_storage.IFileStorage maxFileSize int } +// NewFileService creates a file service. func NewFileService( fileStorage file_storage.IFileStorage, maxFileSize int, @@ -149,6 +151,7 @@ func contentTypeCategory(sniffed string) string { } } +// DownloadFile returns a file for download. func (s *FileService) DownloadFile( ctx context.Context, filename string, diff --git a/internal/services/game_model/game.go b/internal/services/game_model/game.go index e246809..483f23e 100644 --- a/internal/services/game_model/game.go +++ b/internal/services/game_model/game.go @@ -6,6 +6,7 @@ import ( "time" ) +// Game describes a game exposed by the service layer. type Game struct { Format string ID int diff --git a/internal/services/game_model/game_mapper.go b/internal/services/game_model/game_mapper.go index d3f8dfd..5c2101f 100644 --- a/internal/services/game_model/game_mapper.go +++ b/internal/services/game_model/game_mapper.go @@ -5,6 +5,7 @@ import ( "evening_detective_server/internal/services/scenarios_service" ) +// MapGame converts a repository game to a service model. func MapGame( o *repos.Game, domain string, @@ -24,6 +25,7 @@ func MapGame( } } +// MapGames converts repository games to service models. func MapGames( o []*repos.Game, domain string, diff --git a/internal/services/game_model/team.go b/internal/services/game_model/team.go index 97151e7..b86321d 100644 --- a/internal/services/game_model/team.go +++ b/internal/services/game_model/team.go @@ -2,6 +2,7 @@ package game_model import "evening_detective_server/internal/modules/storytelling" +// Team describes a team exposed by the service layer. type Team struct { ParticipationStatus string ParticipationSource string diff --git a/internal/services/game_model/team_mapper.go b/internal/services/game_model/team_mapper.go index 3a35061..2a4de6f 100644 --- a/internal/services/game_model/team_mapper.go +++ b/internal/services/game_model/team_mapper.go @@ -2,6 +2,7 @@ package game_model import "evening_detective_server/internal/repos" +// MapTeam converts a repository team to a service model. func MapTeam( o *repos.Team, ) *Team { @@ -19,6 +20,7 @@ func MapTeam( } } +// MapTeams converts repository teams to service models. func MapTeams( o []*repos.Team, ) []*Team { diff --git a/internal/services/game_service/service.go b/internal/services/game_service/service.go index c98fef6..fcc6ca3 100644 --- a/internal/services/game_service/service.go +++ b/internal/services/game_service/service.go @@ -13,6 +13,7 @@ import ( "time" ) +// GameService handles game lifecycle operations. type GameService struct { gamesRepo *games_repo.GamesRepo teamsRepo *teams_repo.TeamsRepo @@ -30,6 +31,7 @@ var ( ErrInvalidGameFormat = errors.New("Формат игры должен быть online или offline") ) +// NewGameService creates a game service. func NewGameService( gamesRepo *games_repo.GamesRepo, teamsRepo *teams_repo.TeamsRepo, @@ -50,6 +52,7 @@ func NewGameService( } } +// AddGame creates a game. func (s *GameService) AddGame( ctx context.Context, name string, @@ -72,6 +75,7 @@ func (s *GameService) AddGame( return id, nil } +// GetGame returns a game by ID. func (s *GameService) GetGame(ctx context.Context, id int) (*game_model.Game, error) { game, err := s.gamesRepo.GetGameByID(ctx, id) if err != nil { @@ -81,6 +85,7 @@ func (s *GameService) GetGame(ctx context.Context, id int) (*game_model.Game, er return res, nil } +// GetGameMedia returns a game with its media references. func (s *GameService) GetGameMedia(ctx context.Context, id int) (*game_model.Game, error) { game, err := s.gamesRepo.GetGameByID(ctx, id) if err != nil { @@ -94,6 +99,7 @@ func (s *GameService) GetGameMedia(ctx context.Context, id int) (*game_model.Gam return gameResult, nil } +// GetFullGame returns a game with its full related data. func (s *GameService) GetFullGame( ctx context.Context, id int, @@ -141,6 +147,7 @@ func (s *GameService) GetFullGame( return res, nil } +// DeleteGame removes a game. func (s *GameService) DeleteGame( ctx context.Context, id int, @@ -148,6 +155,7 @@ func (s *GameService) DeleteGame( return mapGameError(s.gamesRepo.DeleteGameByID(ctx, id)) } +// GetGames returns games matching the requested filters. func (s *GameService) GetGames( ctx context.Context, ) ([]*game_model.Game, error) { @@ -158,6 +166,7 @@ func (s *GameService) GetGames( return game_model.MapGames(games, s.domain), nil } +// UpdateGame updates a game's editable fields. func (s *GameService) UpdateGame( ctx context.Context, id int, @@ -181,22 +190,27 @@ func (s *GameService) UpdateGame( return mapGameError(s.gamesRepo.UpdateGame(ctx, id, name, description, location, startAt, scenarioID, format)) } +// StartGame marks a game as started. func (s *GameService) StartGame(ctx context.Context, id int) error { return mapGameError(s.gamesRepo.StartGame(ctx, id)) } +// PauseGame marks a game as paused. func (s *GameService) PauseGame(ctx context.Context, id int) error { return mapGameError(s.gamesRepo.PauseGame(ctx, id)) } +// PlayGame resumes a paused game. func (s *GameService) PlayGame(ctx context.Context, id int) error { return mapGameError(s.gamesRepo.PlayGame(ctx, id)) } +// FinishGame marks a game as finished. func (s *GameService) FinishGame(ctx context.Context, id int) error { return mapGameError(s.gamesRepo.FinishGame(ctx, id)) } +// ResetGame resets a game's state. func (s *GameService) ResetGame(ctx context.Context, id int) error { return mapGameError(s.gamesRepo.ResetGame(ctx, id)) } diff --git a/internal/services/gameplay_service/service.go b/internal/services/gameplay_service/service.go index 90f1b53..ca00c7d 100644 --- a/internal/services/gameplay_service/service.go +++ b/internal/services/gameplay_service/service.go @@ -15,10 +15,13 @@ import ( ) var ( + // ErrTeamNotFound indicates that a team was not found. ErrTeamNotFound = errors.New("Команда не найдена") + // ErrGameNotFound indicates that a game was not found. ErrGameNotFound = errors.New("Игра не найдена") ) +// GameplayService handles team gameplay operations. type GameplayService struct { gamesRepo *games_repo.GamesRepo teamsRepo *teams_repo.TeamsRepo @@ -30,6 +33,7 @@ type GameplayService struct { domain string } +// NewGameplayService creates a gameplay service. func NewGameplayService( gamesRepo *games_repo.GamesRepo, teamsRepo *teams_repo.TeamsRepo, @@ -52,10 +56,12 @@ func NewGameplayService( } } +// GiveTeamApplications grants an application to a team. func (s *GameplayService) GiveTeamApplications(ctx context.Context, teamID int, name string) error { return mapGameplayError(s.applicationsRepo.UpdateApplicationStatus(ctx, teamID, name, "done")) } +// GetTeamActions returns the current story, game, and available team actions. func (s *GameplayService) GetTeamActions(ctx context.Context, teamID int, password string) (*storytelling.Story, *game_model.Game, []actions_repo.Action, error) { team, err := s.teamsRepo.GetTeamByID(ctx, teamID) if err != nil { @@ -93,6 +99,7 @@ func (s *GameplayService) GetTeamActions(ctx context.Context, teamID int, passwo return story, game_model.MapGame(game, s.domain), actions, nil } +// AddTeamAction records an action performed by a team. func (s *GameplayService) AddTeamAction(ctx context.Context, teamID int, password string, actionCode string, viaMCP bool) error { team, err := s.teamsRepo.GetTeamByID(ctx, teamID) if err != nil { @@ -147,6 +154,7 @@ func (s *GameplayService) AddTeamAction(ctx context.Context, teamID int, passwor return nil } +// DeleteLastTeamAction removes the latest action for a team. func (s *GameplayService) DeleteLastTeamAction(ctx context.Context, teamID int) error { team, err := s.teamsRepo.GetTeamByID(ctx, teamID) if err != nil { diff --git a/internal/services/map_service/mapper.go b/internal/services/map_service/mapper.go index b258f1f..aa4a3e8 100644 --- a/internal/services/map_service/mapper.go +++ b/internal/services/map_service/mapper.go @@ -3,6 +3,7 @@ package map_service import "evening_detective_server/internal/repos/game_map_repo" +// MapItem describes an item placed on a game map. type MapItem struct { GameID int Kind string diff --git a/internal/services/map_service/service.go b/internal/services/map_service/service.go index b93ceb3..fef0e2f 100644 --- a/internal/services/map_service/service.go +++ b/internal/services/map_service/service.go @@ -7,20 +7,25 @@ import ( "evening_detective_server/internal/repos/game_map_repo" ) +// ErrItemNotFound indicates that a map item was not found. var ErrItemNotFound = errors.New("объект карты не найден") +// MapService handles game map items. type MapService struct { repo *game_map_repo.Repo } +// NewMapService creates a map service. func NewMapService(repo *game_map_repo.Repo) *MapService { return &MapService{repo: repo} } +// Ensure creates a map item if it does not exist. func (s *MapService) Ensure(ctx context.Context, gameID int, kind string, itemID int) error { return s.repo.Ensure(ctx, gameID, kind, itemID) } +// List returns map items for a game. func (s *MapService) List(ctx context.Context, gameID int) ([]*MapItem, error) { items, err := s.repo.List(ctx, gameID) if err != nil { @@ -33,6 +38,7 @@ func (s *MapService) List(ctx context.Context, gameID int) ([]*MapItem, error) { return result, nil } +// Update changes a map item's position and visibility. func (s *MapService) Update(ctx context.Context, item *MapItem) error { if err := s.repo.Update(ctx, toRepoItem(item)); err != nil { if errors.Is(err, game_map_repo.ErrItemNotFound) { @@ -43,6 +49,7 @@ func (s *MapService) Update(ctx context.Context, item *MapItem) error { return nil } +// Delete removes a map item. func (s *MapService) Delete(ctx context.Context, item *MapItem) error { return s.repo.Delete(ctx, toRepoItem(item)) } diff --git a/internal/services/mcp_service/service.go b/internal/services/mcp_service/service.go index 7063dad..c50dc07 100644 --- a/internal/services/mcp_service/service.go +++ b/internal/services/mcp_service/service.go @@ -1,8 +1,3 @@ -// Пакет mcp_service собирает MCP-сервер (Model Context Protocol) для -// игры «Вечерний детектив»: инструменты MCP вызывают игровые сервисы -// напрямую (через интерфейс GamePlayer), без HTTP-прослойки. MCP-сервер -// монтируется в основной процесс как HTTP-эндпоинт /mcp (streamable HTTP) -// на REST-gateway. // Package mcp_service exposes game operations through MCP. package mcp_service @@ -50,10 +45,12 @@ type MCPService struct { answers AnswerSubmitter } +// NewMCPService creates an MCP service. func NewMCPService(game GamePlayer, answers AnswerSubmitter) *MCPService { return &MCPService{game: game, answers: answers} } +// Server returns the underlying MCP server. func (s *MCPService) Server() *server.MCPServer { srv := server.NewMCPServer( "evening-detective-mcp", diff --git a/internal/services/participation_service/participation.go b/internal/services/participation_service/participation.go index 0cf9755..bd539fe 100644 --- a/internal/services/participation_service/participation.go +++ b/internal/services/participation_service/participation.go @@ -19,8 +19,12 @@ type Participation struct { } var ( - ErrParticipationLink = errors.New("Ссылка недействительна") + // ErrParticipationLink indicates that a participation link is invalid. + ErrParticipationLink = errors.New("Ссылка недействительна") + // ErrParticipationClosed indicates that participation answers are closed. ErrParticipationClosed = errors.New("Приём ответов закрыт") - ErrParticipationFinal = errors.New("Решение уже принято и не может быть изменено") - ErrInvalidAnswer = errors.New("Выберите подтверждение или отмену участия") + // ErrParticipationFinal indicates that a final answer cannot be changed. + ErrParticipationFinal = errors.New("Решение уже принято и не может быть изменено") + // ErrInvalidAnswer indicates that the participation answer is invalid. + ErrInvalidAnswer = errors.New("Выберите подтверждение или отмену участия") ) diff --git a/internal/services/participation_service/service.go b/internal/services/participation_service/service.go index 5dec006..af053cc 100644 --- a/internal/services/participation_service/service.go +++ b/internal/services/participation_service/service.go @@ -20,19 +20,24 @@ import ( "github.com/jackc/pgx/v5" ) +// ParticipationService handles team participation. type ParticipationService struct { teamsRepo *teams_repo.TeamsRepo appBaseURL string } +// NewParticipationService creates a participation service. func NewParticipationService(teamsRepo *teams_repo.TeamsRepo, appBaseURL string) *ParticipationService { return &ParticipationService{teamsRepo: teamsRepo, appBaseURL: appBaseURL} } +// GetParticipation returns participation data by token. func (s *ParticipationService) GetParticipation(ctx context.Context, token string) (*Participation, error) { p, err := s.teamsRepo.GetParticipation(ctx, token) return mapParticipation(p), mapParticipationError(err) } + +// AnswerParticipation stores a team's final answer. func (s *ParticipationService) AnswerParticipation(ctx context.Context, token, answer string) (*Participation, error) { p, err := s.teamsRepo.AnswerParticipation(ctx, token, answer) return mapParticipation(p), mapParticipationError(err) @@ -76,6 +81,7 @@ func participationEmail(p *Participation, baseURL, token string, location *time. return email_sender.Message{To: p.Email, Subject: "Подтвердите участие в «Вечернем детективе»", Body: body, HTML: markup} } +// RunParticipationEmails sends pending participation emails. func (s *ParticipationService) RunParticipationEmails(ctx context.Context, sender email_sender.IEmailSender, secret, timezone string) { base, err := url.Parse(s.appBaseURL) if err != nil || base.Host == "" || (base.Scheme != "https" && base.Scheme != "http") || base.User != nil || base.RawQuery != "" || base.Fragment != "" || secret == "" { diff --git a/internal/services/scenarios_service/scenario.go b/internal/services/scenarios_service/scenario.go index ae574cc..425ab2a 100644 --- a/internal/services/scenarios_service/scenario.go +++ b/internal/services/scenarios_service/scenario.go @@ -7,6 +7,7 @@ import ( "time" ) +// Scenario describes a scenario exposed by the service layer. type Scenario struct { ID int Name string diff --git a/internal/services/scenarios_service/scenario_mapper.go b/internal/services/scenarios_service/scenario_mapper.go index 329328e..90c1e1c 100644 --- a/internal/services/scenarios_service/scenario_mapper.go +++ b/internal/services/scenarios_service/scenario_mapper.go @@ -21,6 +21,7 @@ func mapScenariosLight( return res } +// MapScenarioLight converts a repository scenario to a lightweight model. func MapScenarioLight( o *repos.Scenario, domain string, diff --git a/internal/services/scenarios_service/service.go b/internal/services/scenarios_service/service.go index 8a84686..be224fd 100644 --- a/internal/services/scenarios_service/service.go +++ b/internal/services/scenarios_service/service.go @@ -28,6 +28,7 @@ type IScenariosRepository interface { UpdateStoryByScenarioID(ctx context.Context, id int, story string) error } +// ScenarioService handles scenario operations. type ScenarioService struct { scenariosRepo IScenariosRepository cleaner cleaner.ICleaner @@ -36,6 +37,7 @@ type ScenarioService struct { domain string } +// NewScenarioService creates a scenario service. func NewScenarioService( scenariosRepo IScenariosRepository, cleaner cleaner.ICleaner, @@ -52,6 +54,7 @@ func NewScenarioService( } } +// AddScenario creates a scenario. func (s *ScenarioService) AddScenario( ctx context.Context, name string, @@ -64,6 +67,7 @@ func (s *ScenarioService) AddScenario( return id, nil } +// GetScenariosByAuthorID returns scenarios created by an author. func (s *ScenarioService) GetScenariosByAuthorID( ctx context.Context, authorID int, @@ -75,6 +79,7 @@ func (s *ScenarioService) GetScenariosByAuthorID( return mapScenariosLight(scenarios, s.domain), nil } +// GetScenariosCatalog returns scenarios available in the catalog. func (s *ScenarioService) GetScenariosCatalog( ctx context.Context, ) ([]*Scenario, error) { @@ -85,6 +90,7 @@ func (s *ScenarioService) GetScenariosCatalog( return mapScenariosLight(scenarios, s.domain), nil } +// GetFullScenarioByID returns a scenario with its full story. func (s *ScenarioService) GetFullScenarioByID( ctx context.Context, id int, @@ -96,6 +102,7 @@ func (s *ScenarioService) GetFullScenarioByID( return mapScenario(scenario, s.domain) } +// GetScenarioByID returns a scenario by ID. func (s *ScenarioService) GetScenarioByID( ctx context.Context, id int, @@ -107,6 +114,7 @@ func (s *ScenarioService) GetScenarioByID( return MapScenarioLight(scenario, s.domain), nil } +// UpdateScenarioByID updates a scenario's metadata. func (s *ScenarioService) UpdateScenarioByID( ctx context.Context, id int, @@ -177,6 +185,7 @@ func mapScenarioError(err error) error { return err } +// UpdateScenarioStatusByID changes a scenario's publication status. func (s *ScenarioService) UpdateScenarioStatusByID( ctx context.Context, id int, @@ -208,6 +217,7 @@ func (s *ScenarioService) UpdateScenarioStatusByID( return nil } +// DeleteScenarioByID removes a scenario. func (s *ScenarioService) DeleteScenarioByID( ctx context.Context, id int, @@ -229,6 +239,7 @@ func (s *ScenarioService) DeleteScenarioByID( return nil } +// AddScenarioPlace adds a place to a scenario story. func (s *ScenarioService) AddScenarioPlace( ctx context.Context, id int, @@ -250,6 +261,7 @@ func (s *ScenarioService) AddScenarioPlace( return s.updateStory(ctx, id, story) } +// UpdateScenarioPlace updates a place in a scenario story. func (s *ScenarioService) UpdateScenarioPlace( ctx context.Context, id int, @@ -274,6 +286,7 @@ func (s *ScenarioService) UpdateScenarioPlace( return s.updateStory(ctx, id, story) } +// DeleteScenarioPlace removes a place from a scenario story. func (s *ScenarioService) DeleteScenarioPlace( ctx context.Context, id int, @@ -332,6 +345,7 @@ func (s *ScenarioService) UpdateScenarioIntro( return s.updateStory(ctx, id, story) } +// UpdateScenarioConclusion updates a scenario conclusion. func (s *ScenarioService) UpdateScenarioConclusion(ctx context.Context, id int, conclusion *storytelling.Conclusion, actorID int, isAdmin bool) error { if _, err := s.getScenarioForChange(ctx, id, actorID, isAdmin); err != nil { return err diff --git a/internal/services/screen_service/mapper.go b/internal/services/screen_service/mapper.go index e1f66bd..e0714ce 100644 --- a/internal/services/screen_service/mapper.go +++ b/internal/services/screen_service/mapper.go @@ -7,6 +7,7 @@ import ( "evening_detective_server/internal/repos/screen_sessions_repo" ) +// ScreenSession describes a screen session exposed by the service layer. type ScreenSession struct { ID int GameID *int diff --git a/internal/services/screen_service/service.go b/internal/services/screen_service/service.go index 85e1189..78bb7e6 100644 --- a/internal/services/screen_service/service.go +++ b/internal/services/screen_service/service.go @@ -7,20 +7,25 @@ import ( "evening_detective_server/internal/repos/screen_sessions_repo" ) +// ErrScreenNotFound indicates that a screen session was not found. var ErrScreenNotFound = errors.New("Экран не найден") +// ScreenService handles screen sessions. type ScreenService struct { repo *screen_sessions_repo.ScreenSessionsRepo } +// NewScreenService creates a screen service. func NewScreenService(repo *screen_sessions_repo.ScreenSessionsRepo) *ScreenService { return &ScreenService{repo: repo} } +// Create creates a screen session. func (s *ScreenService) Create(ctx context.Context, tokenHash string) (int, error) { return s.repo.Create(ctx, tokenHash) } +// Get returns a screen session. func (s *ScreenService) Get(ctx context.Context, id int, tokenHash string) (*ScreenSession, error) { session, err := s.repo.Get(ctx, id, tokenHash) if err != nil { @@ -29,6 +34,7 @@ func (s *ScreenService) Get(ctx context.Context, id int, tokenHash string) (*Scr return mapSession(session), nil } +// ListByGame returns screen sessions for a game. func (s *ScreenService) ListByGame(ctx context.Context, gameID int) ([]*ScreenSession, error) { sessions, err := s.repo.ListByGame(ctx, gameID) if err != nil { @@ -41,30 +47,37 @@ func (s *ScreenService) ListByGame(ctx context.Context, gameID int) ([]*ScreenSe return result, nil } +// SetMode updates the display mode for a game. func (s *ScreenService) SetMode(ctx context.Context, gameID int, mode int32) error { return s.repo.SetMode(ctx, gameID, mode) } +// SetTimer updates the display timer for a game. func (s *ScreenService) SetTimer(ctx context.Context, gameID int, duration int64, label string) error { return s.repo.SetTimer(ctx, gameID, duration, label) } +// SetTimerCommand updates the timer command for a game. func (s *ScreenService) SetTimerCommand(ctx context.Context, gameID int, command int32) error { return s.repo.SetTimerCommand(ctx, gameID, command) } +// SetCommand updates the display command for a game. func (s *ScreenService) SetCommand(ctx context.Context, gameID int, command int32, position int64, volume int32) error { return s.repo.SetCommand(ctx, gameID, command, position, volume) } +// Bind attaches a screen session to a game. func (s *ScreenService) Bind(ctx context.Context, id, gameID int, tokenHash string) error { return mapError(s.repo.Bind(ctx, id, gameID, tokenHash)) } +// Unbind detaches a screen session from its game. func (s *ScreenService) Unbind(ctx context.Context, id int) error { return mapError(s.repo.Unbind(ctx, id)) } +// SetSound enables or disables sound for a screen session. func (s *ScreenService) SetSound(ctx context.Context, id int, enabled bool) error { return mapError(s.repo.SetSound(ctx, id, enabled)) } diff --git a/internal/services/team_service/registration.go b/internal/services/team_service/registration.go index 90a9c80..4e8e95f 100644 --- a/internal/services/team_service/registration.go +++ b/internal/services/team_service/registration.go @@ -21,12 +21,14 @@ var ( ErrRegistrationClosed = errors.New("Игра не найдена или регистрация уже закрыта") ) +// Registration contains data required to register a team. type Registration struct { Name string `json:"name"` Email string `json:"email"` ParticipantsCount int32 `json:"participantsCount"` } +// UnmarshalJSON decodes and normalizes registration data. func (r *Registration) UnmarshalJSON(data []byte) error { var payload struct { Name string `json:"name"` @@ -63,6 +65,7 @@ func validCaptainEmail(email string) bool { return err == nil && address.Address == email && len(email) <= 254 } +// Validate checks registration data. func (r *Registration) Validate() error { r.Name = strings.TrimSpace(r.Name) r.Email = strings.TrimSpace(r.Email) diff --git a/internal/services/team_service/service.go b/internal/services/team_service/service.go index 9751d66..2985d73 100644 --- a/internal/services/team_service/service.go +++ b/internal/services/team_service/service.go @@ -17,6 +17,7 @@ import ( "evening_detective_server/internal/services/game_model" ) +// TeamService handles team operations. type TeamService struct { gamesRepo *games_repo.GamesRepo teamsRepo *teams_repo.TeamsRepo @@ -25,6 +26,7 @@ type TeamService struct { emailSender email_sender.IEmailSender } +// NewTeamService creates a team service. func NewTeamService( gamesRepo *games_repo.GamesRepo, teamsRepo *teams_repo.TeamsRepo, @@ -41,6 +43,7 @@ func NewTeamService( } } +// AddTeam creates a team. func (s *TeamService) AddTeam( ctx context.Context, creatorID int, @@ -73,6 +76,7 @@ func (s *TeamService) AddTeam( }, nil } +// UpdateTeam changes a team's name. func (s *TeamService) UpdateTeam(ctx context.Context, teamID int, name string) error { err := s.teamsRepo.UpdateTeam(ctx, teamID, name) if errors.Is(err, teams_repo.ErrTeamNotFound) { @@ -81,6 +85,7 @@ func (s *TeamService) UpdateTeam(ctx context.Context, teamID int, name string) e return err } +// DeleteTeam removes a team. func (s *TeamService) DeleteTeam(ctx context.Context, teamID int) error { err := s.teamsRepo.DeleteTeam(ctx, teamID) if errors.Is(err, teams_repo.ErrTeamNotFound) { @@ -89,6 +94,7 @@ func (s *TeamService) DeleteTeam(ctx context.Context, teamID int) error { return err } +// RegisterTeam registers a team for a game. func (s *TeamService) RegisterTeam(ctx context.Context, gameID int, deliveryID string, registration Registration) (int, error) { if err := registration.Validate(); err != nil { return 0, err @@ -133,6 +139,7 @@ func registrationEmail(team *repos.Team, gameName, baseURL string) email_sender. return email_sender.Message{To: team.CaptainEmail, Subject: "Регистрация команды в «Вечернем детективе»", Body: body, HTML: markup} } +// DownloadTeamQR returns a PDF with team QR codes. func (s *TeamService) DownloadTeamQR(ctx context.Context, id int) ([]byte, string, error) { game, err := s.gamesRepo.GetGameByID(ctx, id) if err != nil {