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