From 2f54576a39a7f9ecd964247a2526e0b327b1ed80 Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Thu, 17 Sep 2026 02:47:00 +0700 Subject: [PATCH] add --- cmd/evening_detective_server/main.go | 8 +- internal/app/server.go | 4 + internal/app/teams.go | 8 +- .../game_service/application_mapper.go | 18 -- .../game_service/application_mapper_test.go | 61 ------ internal/services/game_service/service.go | 141 -------------- .../gameplay_service/application_mapper.go | 27 +++ .../application_mapper_test.go | 69 +++++++ .../finished_story_test.go | 4 +- internal/services/gameplay_service/service.go | 179 ++++++++++++++++++ .../team_story.go | 2 +- .../team_story_test.go | 2 +- internal/services/mcp_service/service.go | 2 +- .../participation_test.go | 8 +- 14 files changed, 297 insertions(+), 236 deletions(-) create mode 100644 internal/services/gameplay_service/application_mapper.go create mode 100644 internal/services/gameplay_service/application_mapper_test.go rename internal/services/{game_service => gameplay_service}/finished_story_test.go (94%) create mode 100644 internal/services/gameplay_service/service.go rename internal/services/{game_service => gameplay_service}/team_story.go (95%) rename internal/services/{game_service => gameplay_service}/team_story_test.go (98%) diff --git a/cmd/evening_detective_server/main.go b/cmd/evening_detective_server/main.go index 4440f76..217543d 100644 --- a/cmd/evening_detective_server/main.go +++ b/cmd/evening_detective_server/main.go @@ -27,6 +27,7 @@ import ( "evening_detective_server/internal/services/answer_service" "evening_detective_server/internal/services/file_service" "evening_detective_server/internal/services/game_service" + "evening_detective_server/internal/services/gameplay_service" "evening_detective_server/internal/services/mcp_service" "evening_detective_server/internal/services/participation_service" "evening_detective_server/internal/services/scenarios_service" @@ -141,12 +142,12 @@ func main() { scenariosRepo, scenarioService, os.Getenv("FILE_PREFIX_DOMAIN"), - storyteller, actionsRepo, applicationsRepo, - cleaner, ) + gameplayService := gameplay_service.NewGameplayService(gameRepo, teamRepo, scenarioService, storyteller, actionsRepo, applicationsRepo, cleaner, os.Getenv("FILE_PREFIX_DOMAIN")) + answerService := answer_service.NewAnswerService(gameRepo, teamRepo, scenarioService, answersRepo, actionsRepo) teamService := team_service.NewTeamService(gameRepo, teamRepo, passwordGenerator, team_qr_pdf.NewGenerator(), os.Getenv("APP_BASE_URL")) @@ -201,6 +202,7 @@ func main() { fileService, scenarioService, gameService, + gameplayService, answerService, teamService, participationService, @@ -309,7 +311,7 @@ func main() { // REST /api/teams/{id}/story). Путь /api/mcp длиннее /api/, поэтому // http.ServeMux отдаёт MCP-запросы этому хендлеру, а не gateway. mcpHTTP := server.NewStreamableHTTPServer( - mcp_service.NewMCPService(gameService, answerService).Server(), + mcp_service.NewMCPService(gameplayService, answerService).Server(), server.WithDisableLocalhostProtection(true), ) mainMux.Handle("/api/mcp", mcpHTTP) diff --git a/internal/app/server.go b/internal/app/server.go index a6b4d24..0dfc4ad 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -7,6 +7,7 @@ import ( "evening_detective_server/internal/services/answer_service" "evening_detective_server/internal/services/file_service" "evening_detective_server/internal/services/game_service" + "evening_detective_server/internal/services/gameplay_service" "evening_detective_server/internal/services/participation_service" "evening_detective_server/internal/services/scenarios_service" "evening_detective_server/internal/services/team_service" @@ -26,6 +27,7 @@ type server struct { fileService *file_service.FileService scenarioService *scenarios_service.ScenarioService gameService *game_service.GameService + gameplayService *gameplay_service.GameplayService answerService *answer_service.AnswerService teamService *team_service.TeamService participationService *participation_service.ParticipationService @@ -39,6 +41,7 @@ func NewServer( fileService *file_service.FileService, scenarioService *scenarios_service.ScenarioService, gameService *game_service.GameService, + gameplayService *gameplay_service.GameplayService, answerService *answer_service.AnswerService, teamService *team_service.TeamService, participationService *participation_service.ParticipationService, @@ -51,6 +54,7 @@ func NewServer( fileService: fileService, scenarioService: scenarioService, gameService: gameService, + gameplayService: gameplayService, answerService: answerService, teamService: teamService, participationService: participationService, diff --git a/internal/app/teams.go b/internal/app/teams.go index 8b232e7..b314ad7 100644 --- a/internal/app/teams.go +++ b/internal/app/teams.go @@ -81,7 +81,7 @@ func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamAp return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - err := s.gameService.GiveTeamApplications( + err := s.gameplayService.GiveTeamApplications( ctx, int(req.Id), req.Name, @@ -96,7 +96,7 @@ func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamAp } func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) (*proto.GetTeamStoryRsp, error) { - story, game, actions, err := s.gameService.GetTeamActions( + story, game, actions, err := s.gameplayService.GetTeamActions( ctx, int(req.Id), req.Password, @@ -119,7 +119,7 @@ func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) ( } func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) { - err := s.gameService.AddTeamAction( + err := s.gameplayService.AddTeamAction( ctx, int(req.Id), req.Password, @@ -141,7 +141,7 @@ func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLast return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - err := s.gameService.DeleteLastTeamAction( + err := s.gameplayService.DeleteLastTeamAction( ctx, int(req.Id), ) diff --git a/internal/services/game_service/application_mapper.go b/internal/services/game_service/application_mapper.go index 1aec642..37c4649 100644 --- a/internal/services/game_service/application_mapper.go +++ b/internal/services/game_service/application_mapper.go @@ -32,24 +32,6 @@ func mapApplication(o *repos.Application, domain string) *storytelling.Applicati return app } -// newApplication — улика для сохранения команде (AddTeamAction): имена -// приходят из истории уже с доменным префиксом (mapStory), в БД хранятся -// относительные пути — префикс снимается идемпотентно; file_type -// деривируется при пустом значении. Name не меняется (очистка — в -// вызывающем коде, cleaner'ом). -func newApplication(app *storytelling.Application, domain string) *repos.Application { - image := strings.TrimPrefix(app.Image, domain) - fileType := app.FileType - if fileType == "" { - fileType = file_storage.FileType(image) - } - return &repos.Application{ - Name: app.Name, - Image: image, - FileType: fileType, - } -} - // prefixDomain — идемпотентная префиксация ссылки доменом хранилища // (пустые ссылки и внешние http/https URL не трогаются). func prefixDomain(ref, domain string) string { diff --git a/internal/services/game_service/application_mapper_test.go b/internal/services/game_service/application_mapper_test.go index 4727d9d..fa3f38e 100644 --- a/internal/services/game_service/application_mapper_test.go +++ b/internal/services/game_service/application_mapper_test.go @@ -3,72 +3,11 @@ package game_service import ( "testing" - "evening_detective_server/internal/modules/storytelling" "evening_detective_server/internal/repos" ) const testDomain = "http://storage.test/api/files/" -func TestNewApplication(t *testing.T) { - cases := []struct { - name string - app *storytelling.Application - image string // ожидаемое хранимое имя (без домена) - ftype string // ожидаемый file_type - }{ - { - name: "доменный префикс снимается, file_type сохраняется", - app: &storytelling.Application{ - Name: "Улика", - Image: testDomain + "clue.pdf", - FileType: "pdf", - }, - image: "clue.pdf", - ftype: "pdf", - }, - { - name: "относительное имя не трогается", - app: &storytelling.Application{ - Name: "Улика", - Image: "clue.png", - FileType: "image", - }, - image: "clue.png", - ftype: "image", - }, - { - name: "пустой file_type деривируется из расширения", - app: &storytelling.Application{ - Name: "Улика", - Image: testDomain + "clue.ogg", - }, - image: "clue.ogg", - ftype: "audio", - }, - { - name: "внешний URL снимается как префикс только при совпадении домена", - app: &storytelling.Application{ - Name: "Улика", - Image: "https://example.com/clue.pdf", - }, - image: "https://example.com/clue.pdf", - ftype: "pdf", - }, - } - for _, c := range cases { - got := newApplication(c.app, testDomain) - if got.Name != c.app.Name { - t.Errorf("%s: Name = %q, want %q", c.name, got.Name, c.app.Name) - } - if got.Image != c.image { - t.Errorf("%s: Image = %q, want %q", c.name, got.Image, c.image) - } - if got.FileType != c.ftype { - t.Errorf("%s: FileType = %q, want %q", c.name, got.FileType, c.ftype) - } - } -} - func TestMapApplications(t *testing.T) { o := []*repos.Application{ {Name: "Улика", Image: "clue.pdf", FileType: ""}, diff --git a/internal/services/game_service/service.go b/internal/services/game_service/service.go index 58db7bf..7323770 100644 --- a/internal/services/game_service/service.go +++ b/internal/services/game_service/service.go @@ -3,9 +3,7 @@ package game_service import ( "context" "errors" - "evening_detective_server/internal/modules/cleaner" "evening_detective_server/internal/modules/game_model" - "evening_detective_server/internal/modules/storytelling" "evening_detective_server/internal/repos/actions_repo" "evening_detective_server/internal/repos/applications_repo" "evening_detective_server/internal/repos/games_repo" @@ -21,10 +19,8 @@ type GameService struct { scenariosRepo *scenarios_repo.ScenariosRepo scenarioService *scenarios_service.ScenarioService domain string - storyteller storytelling.IStory actionsRepo *actions_repo.ActionsRepo applicationsRepo *applications_repo.ApplicationsRepo - cleaner cleaner.ICleaner } func NewGameService( @@ -33,10 +29,8 @@ func NewGameService( scenariosRepo *scenarios_repo.ScenariosRepo, scenarioService *scenarios_service.ScenarioService, domain string, - storyteller storytelling.IStory, actionsRepo *actions_repo.ActionsRepo, applicationsRepo *applications_repo.ApplicationsRepo, - cleaner cleaner.ICleaner, ) *GameService { return &GameService{ gamesRepo: gamesRepo, @@ -44,10 +38,8 @@ func NewGameService( scenariosRepo: scenariosRepo, scenarioService: scenarioService, domain: domain, - storyteller: storyteller, actionsRepo: actionsRepo, applicationsRepo: applicationsRepo, - cleaner: cleaner, } } @@ -196,136 +188,3 @@ func (s *GameService) FinishGame(ctx context.Context, id int) error { func (s *GameService) ResetGame(ctx context.Context, id int) error { return s.gamesRepo.ResetGame(ctx, id) } - -func (s *GameService) GiveTeamApplications(ctx context.Context, teamId int, name string) error { - return s.applicationsRepo.UpdateApplicationStatus(ctx, teamId, name, "done") -} - -func (s *GameService) 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 { - return nil, nil, nil, err - } - - if team.Password != password || team.ParticipationStatus == "cancelled" { - return nil, nil, nil, teams_repo.ErrTeamNotFound - } - - game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) - if err != nil { - return nil, nil, nil, err - } - if game.Status == "finish" { - return &storytelling.Story{}, game_model.MapGame(game, s.domain), nil, nil - } - - actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) - if err != nil { - return nil, nil, nil, err - } - - scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) - if err != nil { - return nil, nil, nil, err - } - - codes := make([]string, len(actions)) - for i, action := range actions { - codes[i] = action.Code - } - story := s.storyteller.GetStory(scenario.Story, codes) - filterTeamStory(story, game.Format) - return story, game_model.MapGame(game, s.domain), actions, nil -} - -func (s *GameService) AddTeamAction(ctx context.Context, teamId int, password string, actionCode string, viaMCP bool) error { - team, err := s.teamsRepo.GetTeamByID(ctx, teamId) - if err != nil { - return err - } - - if team.Password != password || team.ParticipationStatus == "cancelled" { - return teams_repo.ErrTeamNotFound - } - - _, err = s.actionsRepo.AddAction(ctx, teamId, actionCode, viaMCP) - if err != nil { - return err - } - if team.ParticipationStatus == "pending" { - if err := s.teamsRepo.ConfirmParticipationByFirstAction(ctx, teamId); err != nil { - return err - } - } - - game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) - if err != nil { - return err - } - - actionDetails, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) - if err != nil { - return err - } - - scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) - if err != nil { - return err - } - - actions := make([]string, len(actionDetails)) - for i, action := range actionDetails { - actions[i] = action.Code - } - teamStory := s.storyteller.GetStory(scenario.Story, actions) - - lastPlace := teamStory.Places[len(teamStory.Places)-1] - - for _, application := range lastPlace.Applications { - app := newApplication(application, s.domain) - app.Name = s.cleaner.ClearText(app.Name) - if _, err := s.applicationsRepo.AddApplication(ctx, teamId, app.Name, app.Image, app.FileType); err != nil { - return err - } - } - - return nil -} - -func (s *GameService) DeleteLastTeamAction(ctx context.Context, teamId int) error { - team, err := s.teamsRepo.GetTeamByID(ctx, teamId) - if err != nil { - return err - } - - game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) - if err != nil { - return err - } - - actionDetails, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) - if err != nil { - return err - } - - scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) - if err != nil { - return err - } - - actions := make([]string, len(actionDetails)) - for i, action := range actionDetails { - actions[i] = action.Code - } - teamStory := s.storyteller.GetStory(scenario.Story, actions) - - lastPlace := teamStory.Places[len(teamStory.Places)-1] - - for _, application := range lastPlace.Applications { - if err := s.applicationsRepo.DeleteApplicationByTeamIdAndName(ctx, teamId, application.Name); err != nil { - return err - } - } - - return s.actionsRepo.DeleteLastActionByTeamId(ctx, teamId) -} diff --git a/internal/services/gameplay_service/application_mapper.go b/internal/services/gameplay_service/application_mapper.go new file mode 100644 index 0000000..1fe1699 --- /dev/null +++ b/internal/services/gameplay_service/application_mapper.go @@ -0,0 +1,27 @@ +package gameplay_service + +import ( + "strings" + + "evening_detective_server/internal/modules/file_storage" + "evening_detective_server/internal/modules/storytelling" + "evening_detective_server/internal/repos" +) + +// newApplication — улика для сохранения команде (AddTeamAction): имена +// приходят из истории уже с доменным префиксом (mapStory), в БД хранятся +// относительные пути — префикс снимается идемпотентно; file_type +// деривируется при пустом значении. Name не меняется (очистка — в +// вызывающем коде, cleaner'ом). +func newApplication(app *storytelling.Application, domain string) *repos.Application { + image := strings.TrimPrefix(app.Image, domain) + fileType := app.FileType + if fileType == "" { + fileType = file_storage.FileType(image) + } + return &repos.Application{ + Name: app.Name, + Image: image, + FileType: fileType, + } +} diff --git a/internal/services/gameplay_service/application_mapper_test.go b/internal/services/gameplay_service/application_mapper_test.go new file mode 100644 index 0000000..f726691 --- /dev/null +++ b/internal/services/gameplay_service/application_mapper_test.go @@ -0,0 +1,69 @@ +package gameplay_service + +import ( + "testing" + + "evening_detective_server/internal/modules/storytelling" +) + +const testDomain = "http://storage.test/api/files/" + +func TestNewApplication(t *testing.T) { + cases := []struct { + name string + app *storytelling.Application + image string // ожидаемое хранимое имя (без домена) + ftype string // ожидаемый file_type + }{ + { + name: "доменный префикс снимается, file_type сохраняется", + app: &storytelling.Application{ + Name: "Улика", + Image: testDomain + "clue.pdf", + FileType: "pdf", + }, + image: "clue.pdf", + ftype: "pdf", + }, + { + name: "относительное имя не трогается", + app: &storytelling.Application{ + Name: "Улика", + Image: "clue.png", + FileType: "image", + }, + image: "clue.png", + ftype: "image", + }, + { + name: "пустой file_type деривируется из расширения", + app: &storytelling.Application{ + Name: "Улика", + Image: testDomain + "clue.ogg", + }, + image: "clue.ogg", + ftype: "audio", + }, + { + name: "внешний URL снимается как префикс только при совпадении домена", + app: &storytelling.Application{ + Name: "Улика", + Image: "https://example.com/clue.pdf", + }, + image: "https://example.com/clue.pdf", + ftype: "pdf", + }, + } + for _, c := range cases { + got := newApplication(c.app, testDomain) + if got.Name != c.app.Name { + t.Errorf("%s: Name = %q, want %q", c.name, got.Name, c.app.Name) + } + if got.Image != c.image { + t.Errorf("%s: Image = %q, want %q", c.name, got.Image, c.image) + } + if got.FileType != c.ftype { + t.Errorf("%s: FileType = %q, want %q", c.name, got.FileType, c.ftype) + } + } +} diff --git a/internal/services/game_service/finished_story_test.go b/internal/services/gameplay_service/finished_story_test.go similarity index 94% rename from internal/services/game_service/finished_story_test.go rename to internal/services/gameplay_service/finished_story_test.go index 742570e..bc486dc 100644 --- a/internal/services/game_service/finished_story_test.go +++ b/internal/services/gameplay_service/finished_story_test.go @@ -1,4 +1,4 @@ -package game_service +package gameplay_service import ( "context" @@ -42,7 +42,7 @@ func TestFinishedTeamStoryPostgres(t *testing.T) { t.Fatal(err) } // No action/scenario repositories: finished games must not load either. - s := &GameService{gamesRepo: games_repo.NewGamesRepo(pool), teamsRepo: teams_repo.NewTeamsRepo(pool)} + s := &GameplayService{gamesRepo: games_repo.NewGamesRepo(pool), teamsRepo: teams_repo.NewTeamsRepo(pool)} for _, format := range []string{"online", "offline"} { if _, err := pool.Exec(ctx, `UPDATE games SET format = $1`, format); err != nil { t.Fatal(err) diff --git a/internal/services/gameplay_service/service.go b/internal/services/gameplay_service/service.go new file mode 100644 index 0000000..40a498b --- /dev/null +++ b/internal/services/gameplay_service/service.go @@ -0,0 +1,179 @@ +package gameplay_service + +import ( + "context" + "evening_detective_server/internal/modules/cleaner" + "evening_detective_server/internal/modules/game_model" + "evening_detective_server/internal/modules/storytelling" + "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/teams_repo" + "evening_detective_server/internal/services/scenarios_service" +) + +type GameplayService struct { + gamesRepo *games_repo.GamesRepo + teamsRepo *teams_repo.TeamsRepo + scenarioService *scenarios_service.ScenarioService + storyteller storytelling.IStory + actionsRepo *actions_repo.ActionsRepo + applicationsRepo *applications_repo.ApplicationsRepo + cleaner cleaner.ICleaner + domain string +} + +func NewGameplayService( + gamesRepo *games_repo.GamesRepo, + teamsRepo *teams_repo.TeamsRepo, + scenarioService *scenarios_service.ScenarioService, + storyteller storytelling.IStory, + actionsRepo *actions_repo.ActionsRepo, + applicationsRepo *applications_repo.ApplicationsRepo, + cleaner cleaner.ICleaner, + domain string, +) *GameplayService { + return &GameplayService{ + gamesRepo: gamesRepo, + teamsRepo: teamsRepo, + scenarioService: scenarioService, + storyteller: storyteller, + actionsRepo: actionsRepo, + applicationsRepo: applicationsRepo, + cleaner: cleaner, + domain: domain, + } +} + +func (s *GameplayService) GiveTeamApplications(ctx context.Context, teamId int, name string) error { + return s.applicationsRepo.UpdateApplicationStatus(ctx, teamId, name, "done") +} + +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 { + return nil, nil, nil, err + } + + if team.Password != password || team.ParticipationStatus == "cancelled" { + return nil, nil, nil, teams_repo.ErrTeamNotFound + } + + game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) + if err != nil { + return nil, nil, nil, err + } + if game.Status == "finish" { + return &storytelling.Story{}, game_model.MapGame(game, s.domain), nil, nil + } + + actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) + if err != nil { + return nil, nil, nil, err + } + + scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) + if err != nil { + return nil, nil, nil, err + } + + codes := make([]string, len(actions)) + for i, action := range actions { + codes[i] = action.Code + } + story := s.storyteller.GetStory(scenario.Story, codes) + filterTeamStory(story, game.Format) + return story, game_model.MapGame(game, s.domain), actions, nil +} + +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 { + return err + } + + if team.Password != password || team.ParticipationStatus == "cancelled" { + return teams_repo.ErrTeamNotFound + } + + _, err = s.actionsRepo.AddAction(ctx, teamId, actionCode, viaMCP) + if err != nil { + return err + } + if team.ParticipationStatus == "pending" { + if err := s.teamsRepo.ConfirmParticipationByFirstAction(ctx, teamId); err != nil { + return err + } + } + + game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) + if err != nil { + return err + } + + actionDetails, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) + if err != nil { + return err + } + + scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) + if err != nil { + return err + } + + actions := make([]string, len(actionDetails)) + for i, action := range actionDetails { + actions[i] = action.Code + } + teamStory := s.storyteller.GetStory(scenario.Story, actions) + + lastPlace := teamStory.Places[len(teamStory.Places)-1] + + for _, application := range lastPlace.Applications { + app := newApplication(application, s.domain) + app.Name = s.cleaner.ClearText(app.Name) + if _, err := s.applicationsRepo.AddApplication(ctx, teamId, app.Name, app.Image, app.FileType); err != nil { + return err + } + } + + return nil +} + +func (s *GameplayService) DeleteLastTeamAction(ctx context.Context, teamId int) error { + team, err := s.teamsRepo.GetTeamByID(ctx, teamId) + if err != nil { + return err + } + + game, err := s.gamesRepo.GetGameByID(ctx, team.GameId) + if err != nil { + return err + } + + actionDetails, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId) + if err != nil { + return err + } + + scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId) + if err != nil { + return err + } + + actions := make([]string, len(actionDetails)) + for i, action := range actionDetails { + actions[i] = action.Code + } + teamStory := s.storyteller.GetStory(scenario.Story, actions) + + lastPlace := teamStory.Places[len(teamStory.Places)-1] + + for _, application := range lastPlace.Applications { + if err := s.applicationsRepo.DeleteApplicationByTeamIdAndName(ctx, teamId, application.Name); err != nil { + return err + } + } + + return s.actionsRepo.DeleteLastActionByTeamId(ctx, teamId) +} diff --git a/internal/services/game_service/team_story.go b/internal/services/gameplay_service/team_story.go similarity index 95% rename from internal/services/game_service/team_story.go rename to internal/services/gameplay_service/team_story.go index c391f6d..04446ab 100644 --- a/internal/services/game_service/team_story.go +++ b/internal/services/gameplay_service/team_story.go @@ -1,4 +1,4 @@ -package game_service +package gameplay_service import "evening_detective_server/internal/modules/storytelling" diff --git a/internal/services/game_service/team_story_test.go b/internal/services/gameplay_service/team_story_test.go similarity index 98% rename from internal/services/game_service/team_story_test.go rename to internal/services/gameplay_service/team_story_test.go index b753146..3d31faa 100644 --- a/internal/services/game_service/team_story_test.go +++ b/internal/services/gameplay_service/team_story_test.go @@ -1,4 +1,4 @@ -package game_service +package gameplay_service import ( "testing" diff --git a/internal/services/mcp_service/service.go b/internal/services/mcp_service/service.go index a27ef67..8ca4322 100644 --- a/internal/services/mcp_service/service.go +++ b/internal/services/mcp_service/service.go @@ -28,7 +28,7 @@ import ( const defaultCallTimeout = 10 * time.Second // GamePlayer — граница доступа к игровым сервисам, реализуемая -// *game_service.GameService; интерфейс позволяет тестировать сервис +// *gameplay_service.GameplayService; интерфейс позволяет тестировать сервис // без БД. type GamePlayer interface { GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, *game_model.Game, []actions_repo.Action, error) diff --git a/internal/services/participation_service/participation_test.go b/internal/services/participation_service/participation_test.go index 50a9b53..9589c3d 100644 --- a/internal/services/participation_service/participation_test.go +++ b/internal/services/participation_service/participation_test.go @@ -13,7 +13,7 @@ import ( "evening_detective_server/internal/modules/email_sender" "evening_detective_server/internal/repos/teams_repo" - "evening_detective_server/internal/services/game_service" + "evening_detective_server/internal/services/gameplay_service" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -223,11 +223,11 @@ func TestParticipationPostgres(t *testing.T) { t.Fatal("cancelled team in active list") } } - gameService := game_service.NewGameService(nil, repo, nil, nil, "", nil, nil, nil, nil) - if _, _, _, err := gameService.GetTeamActions(ctx, 3, "secret"); !errors.Is(err, teams_repo.ErrTeamNotFound) { + gameplayService := gameplay_service.NewGameplayService(nil, repo, nil, nil, nil, nil, nil, "") + if _, _, _, err := gameplayService.GetTeamActions(ctx, 3, "secret"); !errors.Is(err, teams_repo.ErrTeamNotFound) { t.Fatalf("cancelled team got story: %v", err) } - if err := gameService.AddTeamAction(ctx, 3, "secret", "anything", false); !errors.Is(err, teams_repo.ErrTeamNotFound) { + if err := gameplayService.AddTeamAction(ctx, 3, "secret", "anything", false); !errors.Is(err, teams_repo.ErrTeamNotFound) { t.Fatalf("cancelled team added action: %v", err) } answerService := answer_service.NewAnswerService(nil, repo, nil, nil, nil)