package game_service import ( "context" "errors" "time" "evening_detective_server/internal/modules/team_qr_pdf" "evening_detective_server/internal/repos/actions_repo" "evening_detective_server/internal/repos/applications_repo" "evening_detective_server/internal/repos/games_repo" "evening_detective_server/internal/repos/scenarios_repo" "evening_detective_server/internal/repos/teams_repo" "evening_detective_server/internal/services/game_model" "evening_detective_server/internal/services/scenarios_service" ) // GameService handles game lifecycle operations. type GameService struct { gamesRepo *games_repo.GamesRepo teamsRepo *teams_repo.TeamsRepo scenariosRepo *scenarios_repo.ScenariosRepo scenarioService *scenarios_service.ScenarioService domain string actionsRepo *actions_repo.ActionsRepo applicationsRepo *applications_repo.ApplicationsRepo } var ( ErrGameNotFound = errors.New("игра не найдена") ErrInvalidGameFormat = errors.New("формат игры должен быть online или offline") ) // NewGameService creates a game service. func NewGameService( gamesRepo *games_repo.GamesRepo, teamsRepo *teams_repo.TeamsRepo, scenariosRepo *scenarios_repo.ScenariosRepo, scenarioService *scenarios_service.ScenarioService, domain string, actionsRepo *actions_repo.ActionsRepo, applicationsRepo *applications_repo.ApplicationsRepo, ) *GameService { return &GameService{ gamesRepo: gamesRepo, teamsRepo: teamsRepo, scenariosRepo: scenariosRepo, scenarioService: scenarioService, domain: domain, actionsRepo: actionsRepo, applicationsRepo: applicationsRepo, } } // AddGame creates a game. func (s *GameService) AddGame( ctx context.Context, name string, description string, location *string, startAt time.Time, scenarioID int32, format string, ) (int, error) { if format == "" { format = "online" } if format != "online" && format != "offline" { return 0, ErrInvalidGameFormat } id, err := s.gamesRepo.AddGame(ctx, name, description, location, startAt, scenarioID, format) if err != nil { return 0, err } 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 { return nil, mapGameError(err) } res := game_model.MapGame(game, s.domain) 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 { return nil, mapGameError(err) } gameResult := game_model.MapGame(game, s.domain) gameResult.Scenario, err = s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioID) if err != nil { return nil, err } return gameResult, nil } // GetFullGame returns a game with its full related data. func (s *GameService) GetFullGame( ctx context.Context, id int, ) (*game_model.Game, error) { game, err := s.gamesRepo.GetGameByID(ctx, id) if err != nil { return nil, mapGameError(err) } scenario, err := s.scenariosRepo.GetScenarioByID(ctx, game.ScenarioID) if err != nil { if errors.Is(err, scenarios_repo.ErrScenarioNotFound) { return nil, scenarios_service.ErrScenarioNotFound } return nil, err } game.Scenario = scenario teams, err := s.teamsRepo.GetTeamsByGameID(ctx, id) if err != nil { return nil, err } game.Teams = teams res := game_model.MapGame(game, s.domain) teamIDs := make([]string, 0, len(game.Teams)) for _, team := range game.Teams { teamIDs = append(teamIDs, team.ID) } actionsCountByTeamIDs, err := s.actionsRepo.GetActionsCountByTeamIDs(ctx, teamIDs) if err != nil { return nil, err } needApplicationsByTeamIDs, err := s.applicationsRepo.GetApplicationsByTeamIDsAndState(ctx, teamIDs, "need") if err != nil { return nil, err } for _, team := range res.Teams { team.ActionsCount = actionsCountByTeamIDs[team.ID] team.Applications = mapApplications(needApplicationsByTeamIDs[team.ID], s.domain) } return res, nil } // DeleteGame removes a game. func (s *GameService) DeleteGame( ctx context.Context, id int, ) error { 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) { games, err := s.gamesRepo.GetGames(ctx) if err != nil { return nil, err } return game_model.MapGames(games, s.domain), nil } // UpdateGame updates a game's editable fields. func (s *GameService) UpdateGame( ctx context.Context, id int, name string, description string, location *string, startAt time.Time, scenarioID int32, format string, wifiName, wifiPassword *string, ) error { nameValue, passwordValue := "", "" if wifiName != nil { nameValue = *wifiName } if wifiPassword != nil { passwordValue = *wifiPassword } if err := team_qr_pdf.ValidateWiFi(nameValue, passwordValue); err != nil { return err } if format == "" { game, err := s.gamesRepo.GetGameByID(ctx, id) if err != nil { return mapGameError(err) } format = game.Format } if format != "online" && format != "offline" { return ErrInvalidGameFormat } return mapGameError(s.gamesRepo.UpdateGame(ctx, id, name, description, location, startAt, scenarioID, format, wifiName, wifiPassword)) } // 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)) } func mapGameError(err error) error { if errors.Is(err, games_repo.ErrGameNotFound) { return ErrGameNotFound } return err }