This commit is contained in:
2026-09-17 02:47:00 +07:00
parent 87bd07019a
commit 2f54576a39
14 changed files with 297 additions and 236 deletions
+5 -3
View File
@@ -27,6 +27,7 @@ import (
"evening_detective_server/internal/services/answer_service" "evening_detective_server/internal/services/answer_service"
"evening_detective_server/internal/services/file_service" "evening_detective_server/internal/services/file_service"
"evening_detective_server/internal/services/game_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/mcp_service"
"evening_detective_server/internal/services/participation_service" "evening_detective_server/internal/services/participation_service"
"evening_detective_server/internal/services/scenarios_service" "evening_detective_server/internal/services/scenarios_service"
@@ -141,12 +142,12 @@ func main() {
scenariosRepo, scenariosRepo,
scenarioService, scenarioService,
os.Getenv("FILE_PREFIX_DOMAIN"), os.Getenv("FILE_PREFIX_DOMAIN"),
storyteller,
actionsRepo, actionsRepo,
applicationsRepo, 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) 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")) teamService := team_service.NewTeamService(gameRepo, teamRepo, passwordGenerator, team_qr_pdf.NewGenerator(), os.Getenv("APP_BASE_URL"))
@@ -201,6 +202,7 @@ func main() {
fileService, fileService,
scenarioService, scenarioService,
gameService, gameService,
gameplayService,
answerService, answerService,
teamService, teamService,
participationService, participationService,
@@ -309,7 +311,7 @@ func main() {
// REST /api/teams/{id}/story). Путь /api/mcp длиннее /api/, поэтому // REST /api/teams/{id}/story). Путь /api/mcp длиннее /api/, поэтому
// http.ServeMux отдаёт MCP-запросы этому хендлеру, а не gateway. // http.ServeMux отдаёт MCP-запросы этому хендлеру, а не gateway.
mcpHTTP := server.NewStreamableHTTPServer( mcpHTTP := server.NewStreamableHTTPServer(
mcp_service.NewMCPService(gameService, answerService).Server(), mcp_service.NewMCPService(gameplayService, answerService).Server(),
server.WithDisableLocalhostProtection(true), server.WithDisableLocalhostProtection(true),
) )
mainMux.Handle("/api/mcp", mcpHTTP) mainMux.Handle("/api/mcp", mcpHTTP)
+4
View File
@@ -7,6 +7,7 @@ import (
"evening_detective_server/internal/services/answer_service" "evening_detective_server/internal/services/answer_service"
"evening_detective_server/internal/services/file_service" "evening_detective_server/internal/services/file_service"
"evening_detective_server/internal/services/game_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/participation_service"
"evening_detective_server/internal/services/scenarios_service" "evening_detective_server/internal/services/scenarios_service"
"evening_detective_server/internal/services/team_service" "evening_detective_server/internal/services/team_service"
@@ -26,6 +27,7 @@ type server struct {
fileService *file_service.FileService fileService *file_service.FileService
scenarioService *scenarios_service.ScenarioService scenarioService *scenarios_service.ScenarioService
gameService *game_service.GameService gameService *game_service.GameService
gameplayService *gameplay_service.GameplayService
answerService *answer_service.AnswerService answerService *answer_service.AnswerService
teamService *team_service.TeamService teamService *team_service.TeamService
participationService *participation_service.ParticipationService participationService *participation_service.ParticipationService
@@ -39,6 +41,7 @@ func NewServer(
fileService *file_service.FileService, fileService *file_service.FileService,
scenarioService *scenarios_service.ScenarioService, scenarioService *scenarios_service.ScenarioService,
gameService *game_service.GameService, gameService *game_service.GameService,
gameplayService *gameplay_service.GameplayService,
answerService *answer_service.AnswerService, answerService *answer_service.AnswerService,
teamService *team_service.TeamService, teamService *team_service.TeamService,
participationService *participation_service.ParticipationService, participationService *participation_service.ParticipationService,
@@ -51,6 +54,7 @@ func NewServer(
fileService: fileService, fileService: fileService,
scenarioService: scenarioService, scenarioService: scenarioService,
gameService: gameService, gameService: gameService,
gameplayService: gameplayService,
answerService: answerService, answerService: answerService,
teamService: teamService, teamService: teamService,
participationService: participationService, participationService: participationService,
+4 -4
View File
@@ -81,7 +81,7 @@ func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamAp
return nil, status.Errorf(codes.PermissionDenied, "permission denied") return nil, status.Errorf(codes.PermissionDenied, "permission denied")
} }
err := s.gameService.GiveTeamApplications( err := s.gameplayService.GiveTeamApplications(
ctx, ctx,
int(req.Id), int(req.Id),
req.Name, 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) { 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, ctx,
int(req.Id), int(req.Id),
req.Password, 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) { func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) {
err := s.gameService.AddTeamAction( err := s.gameplayService.AddTeamAction(
ctx, ctx,
int(req.Id), int(req.Id),
req.Password, req.Password,
@@ -141,7 +141,7 @@ func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLast
return nil, status.Errorf(codes.PermissionDenied, "permission denied") return nil, status.Errorf(codes.PermissionDenied, "permission denied")
} }
err := s.gameService.DeleteLastTeamAction( err := s.gameplayService.DeleteLastTeamAction(
ctx, ctx,
int(req.Id), int(req.Id),
) )
@@ -32,24 +32,6 @@ func mapApplication(o *repos.Application, domain string) *storytelling.Applicati
return app 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 — идемпотентная префиксация ссылки доменом хранилища // prefixDomain — идемпотентная префиксация ссылки доменом хранилища
// (пустые ссылки и внешние http/https URL не трогаются). // (пустые ссылки и внешние http/https URL не трогаются).
func prefixDomain(ref, domain string) string { func prefixDomain(ref, domain string) string {
@@ -3,72 +3,11 @@ package game_service
import ( import (
"testing" "testing"
"evening_detective_server/internal/modules/storytelling"
"evening_detective_server/internal/repos" "evening_detective_server/internal/repos"
) )
const testDomain = "http://storage.test/api/files/" 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) { func TestMapApplications(t *testing.T) {
o := []*repos.Application{ o := []*repos.Application{
{Name: "Улика", Image: "clue.pdf", FileType: ""}, {Name: "Улика", Image: "clue.pdf", FileType: ""},
-141
View File
@@ -3,9 +3,7 @@ package game_service
import ( import (
"context" "context"
"errors" "errors"
"evening_detective_server/internal/modules/cleaner"
"evening_detective_server/internal/modules/game_model" "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/actions_repo"
"evening_detective_server/internal/repos/applications_repo" "evening_detective_server/internal/repos/applications_repo"
"evening_detective_server/internal/repos/games_repo" "evening_detective_server/internal/repos/games_repo"
@@ -21,10 +19,8 @@ type GameService struct {
scenariosRepo *scenarios_repo.ScenariosRepo scenariosRepo *scenarios_repo.ScenariosRepo
scenarioService *scenarios_service.ScenarioService scenarioService *scenarios_service.ScenarioService
domain string domain string
storyteller storytelling.IStory
actionsRepo *actions_repo.ActionsRepo actionsRepo *actions_repo.ActionsRepo
applicationsRepo *applications_repo.ApplicationsRepo applicationsRepo *applications_repo.ApplicationsRepo
cleaner cleaner.ICleaner
} }
func NewGameService( func NewGameService(
@@ -33,10 +29,8 @@ func NewGameService(
scenariosRepo *scenarios_repo.ScenariosRepo, scenariosRepo *scenarios_repo.ScenariosRepo,
scenarioService *scenarios_service.ScenarioService, scenarioService *scenarios_service.ScenarioService,
domain string, domain string,
storyteller storytelling.IStory,
actionsRepo *actions_repo.ActionsRepo, actionsRepo *actions_repo.ActionsRepo,
applicationsRepo *applications_repo.ApplicationsRepo, applicationsRepo *applications_repo.ApplicationsRepo,
cleaner cleaner.ICleaner,
) *GameService { ) *GameService {
return &GameService{ return &GameService{
gamesRepo: gamesRepo, gamesRepo: gamesRepo,
@@ -44,10 +38,8 @@ func NewGameService(
scenariosRepo: scenariosRepo, scenariosRepo: scenariosRepo,
scenarioService: scenarioService, scenarioService: scenarioService,
domain: domain, domain: domain,
storyteller: storyteller,
actionsRepo: actionsRepo, actionsRepo: actionsRepo,
applicationsRepo: applicationsRepo, 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 { func (s *GameService) ResetGame(ctx context.Context, id int) error {
return s.gamesRepo.ResetGame(ctx, id) 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)
}
@@ -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,
}
}
@@ -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)
}
}
}
@@ -1,4 +1,4 @@
package game_service package gameplay_service
import ( import (
"context" "context"
@@ -42,7 +42,7 @@ func TestFinishedTeamStoryPostgres(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// No action/scenario repositories: finished games must not load either. // 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"} { for _, format := range []string{"online", "offline"} {
if _, err := pool.Exec(ctx, `UPDATE games SET format = $1`, format); err != nil { if _, err := pool.Exec(ctx, `UPDATE games SET format = $1`, format); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -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)
}
@@ -1,4 +1,4 @@
package game_service package gameplay_service
import "evening_detective_server/internal/modules/storytelling" import "evening_detective_server/internal/modules/storytelling"
@@ -1,4 +1,4 @@
package game_service package gameplay_service
import ( import (
"testing" "testing"
+1 -1
View File
@@ -28,7 +28,7 @@ import (
const defaultCallTimeout = 10 * time.Second const defaultCallTimeout = 10 * time.Second
// GamePlayer — граница доступа к игровым сервисам, реализуемая // GamePlayer — граница доступа к игровым сервисам, реализуемая
// *game_service.GameService; интерфейс позволяет тестировать сервис // *gameplay_service.GameplayService; интерфейс позволяет тестировать сервис
// без БД. // без БД.
type GamePlayer interface { type GamePlayer interface {
GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, *game_model.Game, []actions_repo.Action, error) GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, *game_model.Game, []actions_repo.Action, error)
@@ -13,7 +13,7 @@ import (
"evening_detective_server/internal/modules/email_sender" "evening_detective_server/internal/modules/email_sender"
"evening_detective_server/internal/repos/teams_repo" "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"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
@@ -223,11 +223,11 @@ func TestParticipationPostgres(t *testing.T) {
t.Fatal("cancelled team in active list") t.Fatal("cancelled team in active list")
} }
} }
gameService := game_service.NewGameService(nil, repo, nil, nil, "", nil, nil, nil, nil) gameplayService := gameplay_service.NewGameplayService(nil, repo, nil, nil, nil, nil, nil, "")
if _, _, _, err := gameService.GetTeamActions(ctx, 3, "secret"); !errors.Is(err, teams_repo.ErrTeamNotFound) { if _, _, _, err := gameplayService.GetTeamActions(ctx, 3, "secret"); !errors.Is(err, teams_repo.ErrTeamNotFound) {
t.Fatalf("cancelled team got story: %v", err) 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) t.Fatalf("cancelled team added action: %v", err)
} }
answerService := answer_service.NewAnswerService(nil, repo, nil, nil, nil) answerService := answer_service.NewAnswerService(nil, repo, nil, nil, nil)