This commit is contained in:
2026-07-28 20:38:03 +07:00
parent f6934e9d5b
commit 20662dc9da
20 changed files with 729 additions and 233 deletions
+51 -5
View File
@@ -605,15 +605,51 @@ func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamAp
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
panic("unimplemented")
err := s.gameService.GiveTeamApplications(
ctx,
int(req.Id),
req.Name,
)
if err != nil {
return &proto.GiveTeamApplicationsRsp{
Error: err.Error(),
}, nil
}
return &proto.GiveTeamApplicationsRsp{}, nil
}
func (s *server) GetTeamActions(ctx context.Context, req *proto.GetTeamActionsReq) (*proto.GetTeamActionsRsp, error) {
panic("unimplemented")
func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) (*proto.GetTeamStoryRsp, error) {
story, err := s.gameService.GetTeamActions(
ctx,
int(req.Id),
req.Password,
)
if err != nil {
return &proto.GetTeamStoryRsp{
Error: err.Error(),
}, nil
}
return &proto.GetTeamStoryRsp{
Story: mapStory(story),
}, nil
}
func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) {
panic("unimplemented")
err := s.gameService.AddTeamAction(
ctx,
int(req.Id),
req.Password,
req.Code,
)
if err != nil {
return &proto.AddTeamActionRsp{
Error: err.Error(),
}, nil
}
return &proto.AddTeamActionRsp{}, nil
}
func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLastTeamActionReq) (*proto.DeleteLastTeamActionRsp, error) {
@@ -622,5 +658,15 @@ func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLast
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
panic("unimplemented")
err := s.gameService.DeleteLastTeamAction(
ctx,
int(req.Id),
)
if err != nil {
return &proto.DeleteLastTeamActionRsp{
Error: err.Error(),
}, nil
}
return &proto.DeleteLastTeamActionRsp{}, nil
}
+6 -4
View File
@@ -9,10 +9,12 @@ func mapTeam(
o *game_service.Team,
) *proto.Team {
return &proto.Team{
Id: int32(o.ID),
Name: o.Name,
Status: o.Status,
Password: o.Password,
Id: int32(o.ID),
Name: o.Name,
Status: o.Status,
Password: o.Password,
ActionsCount: int32(o.ActionsCount),
Applications: mapApplications(o.Applications),
}
}
+103
View File
@@ -0,0 +1,103 @@
package actions_repo
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
)
type ActionsRepo struct {
pool *pgxpool.Pool
}
func NewActionsRepo(pool *pgxpool.Pool) *ActionsRepo {
return &ActionsRepo{
pool: pool,
}
}
func (r *ActionsRepo) AddAction(ctx context.Context, teamId int, code string) (int, error) {
id := 0
err := r.pool.QueryRow(
ctx,
`INSERT INTO actions (code, team_id)
VALUES ($1, $2)
RETURNING id`,
code,
teamId,
).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
func (r *ActionsRepo) GetActionsByTeamID(ctx context.Context, teamId int) ([]string, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
code
FROM actions
WHERE team_id = $1
ORDER BY created_at ASC`,
teamId,
)
if err != nil {
return nil, err
}
defer rows.Close()
var codes []string
for rows.Next() {
code := ""
err := rows.Scan(
&code,
)
if err != nil {
return nil, err
}
codes = append(codes, code)
}
if err := rows.Err(); err != nil {
return nil, err
}
return codes, nil
}
func (r *ActionsRepo) GetActionsCountByTeamIDs(ctx context.Context, teamIds []int) (map[int]int, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
team_id,
count(*)
FROM actions
WHERE team_id = ANY($1)
GROUP BY team_id`,
teamIds,
)
if err != nil {
return nil, err
}
defer rows.Close()
res := make(map[int]int, len(teamIds))
for rows.Next() {
id := 0
count := 0
err := rows.Scan(
&id,
&count,
)
if err != nil {
return nil, err
}
res[id] = count
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
+7
View File
@@ -0,0 +1,7 @@
package repos
type Application struct {
Name string
Image string
TeamId int
}
+81
View File
@@ -0,0 +1,81 @@
package applications_repo
import (
"context"
"evening_detective_server/internal/repos"
"github.com/jackc/pgx/v5/pgxpool"
)
type ApplicationsRepo struct {
pool *pgxpool.Pool
}
func NewApplicationsRepo(pool *pgxpool.Pool) *ApplicationsRepo {
return &ApplicationsRepo{
pool: pool,
}
}
func (r *ApplicationsRepo) AddApplication(
ctx context.Context,
teamId int,
name string,
image string,
) (int, error) {
id := 0
err := r.pool.QueryRow(
ctx,
`INSERT INTO applications (name, image, team_id)
VALUES ($1, $2, $3)
RETURNING id`,
name,
image,
teamId,
).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
func (r *ApplicationsRepo) GetApplicationsByTeamIDsAndState(
ctx context.Context,
teamIds []int,
state string,
) (map[int][]*repos.Application, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
name,
image,
team_id
FROM applications
WHERE team_id = ANY($1)`,
teamIds,
)
if err != nil {
return nil, err
}
defer rows.Close()
res := make(map[int][]*repos.Application, len(teamIds))
for rows.Next() {
application := &repos.Application{}
err := rows.Scan(
&application.Name,
&application.Image,
&application.TeamId,
)
if err != nil {
return nil, err
}
res[application.TeamId] = append(res[application.TeamId], application)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
+5 -4
View File
@@ -1,8 +1,9 @@
package repos
type Team struct {
ID int
Name string
Status string
Password string
ID int
Name string
Status string
Password string
ScenarioId int
}
+32
View File
@@ -5,6 +5,7 @@ import (
"errors"
"evening_detective_server/internal/repos"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -22,6 +23,37 @@ func NewTeamsRepo(pool *pgxpool.Pool) *TeamsRepo {
}
}
func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error) {
team := &repos.Team{}
row := r.pool.QueryRow(
ctx,
`SELECT
id,
name,
status,
password,
scenario_id
FROM teams
WHERE id = $1`,
id,
)
err := row.Scan(
&team.ID,
&team.Name,
&team.Status,
&team.Password,
&team.ScenarioId,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrTeamNotFound
}
return nil, err
}
return team, nil
}
func (r *TeamsRepo) GetTeamsByGameID(
ctx context.Context,
gameId int,
@@ -0,0 +1,6 @@
package game_service
type Application struct {
Name string
Image string
}
@@ -0,0 +1,21 @@
package game_service
import (
"evening_detective_server/internal/modules/storytelling"
"evening_detective_server/internal/repos"
)
func mapApplications(o []*repos.Application) []*storytelling.Application {
res := make([]*storytelling.Application, 0, len(o))
for _, item := range o {
res = append(res, mapApplication(item))
}
return res
}
func mapApplication(o *repos.Application) *storytelling.Application {
return &storytelling.Application{
Name: o.Name,
Image: o.Image,
}
}
+115 -1
View File
@@ -2,10 +2,15 @@ package game_service
import (
"context"
"evening_detective_server/internal/modules/cleaner"
"evening_detective_server/internal/modules/password_generator"
"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/scenarios_repo"
"evening_detective_server/internal/repos/teams_repo"
"evening_detective_server/internal/services/scenarios_service"
"time"
)
@@ -13,23 +18,38 @@ type GameService struct {
gamesRepo *games_repo.GamesRepo
teamsRepo *teams_repo.TeamsRepo
scenariosRepo *scenarios_repo.ScenariosRepo
scenarioService *scenarios_service.ScenarioService
domain string
passwordGenerator password_generator.IPasswordGenerator
storyteller storytelling.IStory
cleaner cleaner.ICleaner
actionsRepo *actions_repo.ActionsRepo
applicationsRepo *applications_repo.ApplicationsRepo
}
func NewGameService(
gamesRepo *games_repo.GamesRepo,
teamsRepo *teams_repo.TeamsRepo,
scenariosRepo *scenarios_repo.ScenariosRepo,
scenarioService *scenarios_service.ScenarioService,
domain string,
passwordGenerator password_generator.IPasswordGenerator,
storyteller storytelling.IStory,
cleaner cleaner.ICleaner,
actionsRepo *actions_repo.ActionsRepo,
applicationsRepo *applications_repo.ApplicationsRepo,
) *GameService {
return &GameService{
gamesRepo: gamesRepo,
teamsRepo: teamsRepo,
scenariosRepo: scenariosRepo,
scenarioService: scenarioService,
domain: domain,
passwordGenerator: passwordGenerator,
storyteller: storyteller,
cleaner: cleaner,
actionsRepo: actionsRepo,
applicationsRepo: applicationsRepo,
}
}
@@ -65,7 +85,30 @@ func (s *GameService) GetGame(
return nil, err
}
game.Teams = teams
return mapGame(game, s.domain), nil
res := mapGame(game, s.domain)
teamIds := make([]int, 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])
}
return res, nil
}
func (s *GameService) DeleteGame(
@@ -121,3 +164,74 @@ func (s *GameService) UpdateTeam(ctx context.Context, teamId int, name string) e
func (s *GameService) DeleteTeam(ctx context.Context, teamId int) error {
return s.teamsRepo.DeleteTeam(ctx, teamId)
}
func (s *GameService) GiveTeamApplications(ctx context.Context, teamId int, name string) error {
panic("unimplemented")
}
func (s *GameService) GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, error) {
team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
if err != nil {
return nil, err
}
if team.Password != password {
return nil, teams_repo.ErrTeamNotFound
}
actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
if err != nil {
return nil, err
}
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, team.ScenarioId)
if err != nil {
return nil, err
}
return s.storyteller.GetStory(scenario.Story, actions), nil
}
func (s *GameService) AddTeamAction(ctx context.Context, teamId int, password string, actionCode string) error {
team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
if err != nil {
return err
}
if team.Password != password {
return teams_repo.ErrTeamNotFound
}
code := s.cleaner.ClearCode(actionCode)
_, err = s.actionsRepo.AddAction(ctx, teamId, code)
if err != nil {
return err
}
actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
if err != nil {
return err
}
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, team.ScenarioId)
if err != nil {
return err
}
teamStory := s.storyteller.GetStory(scenario.Story, actions)
lastPlace := teamStory.Places[len(teamStory.Places)-1]
for _, application := range lastPlace.Applications {
_, err := s.applicationsRepo.AddApplication(ctx, teamId, application.Name, application.Image)
if err != nil {
return err
}
}
return nil
}
func (s *GameService) DeleteLastTeamAction(ctx context.Context, teamId int) error {
panic("unimplemented")
}
+8 -4
View File
@@ -1,8 +1,12 @@
package game_service
import "evening_detective_server/internal/modules/storytelling"
type Team struct {
ID int
Name string
Status string
Password string
ID int
Name string
Status string
Password string
ActionsCount int
Applications []*storytelling.Application
}
@@ -3,6 +3,7 @@ package scenarios_service
import (
"context"
"errors"
"evening_detective_server/internal/modules/cleaner"
"evening_detective_server/internal/modules/storytelling"
"evening_detective_server/internal/repos/scenarios_repo"
"strings"
@@ -10,15 +11,18 @@ import (
type ScenarioService struct {
scenariosRepo *scenarios_repo.ScenariosRepo
cleaner cleaner.ICleaner
domain string
}
func NewScenarioService(
scenariosRepo *scenarios_repo.ScenariosRepo,
cleaner cleaner.ICleaner,
domain string,
) *ScenarioService {
return &ScenarioService{
scenariosRepo: scenariosRepo,
cleaner: cleaner,
domain: domain,
}
}
@@ -167,7 +171,8 @@ func (s *ScenarioService) getStory(ctx context.Context, id int) (*storytelling.S
func (s *ScenarioService) updateStory(ctx context.Context, id int, story *storytelling.Story) error {
mapCodes := map[string]struct{}{}
for _, place := range story.Places {
mapCodes[place.Code] = struct{}{}
code := s.cleaner.ClearCode(place.Code)
mapCodes[code] = struct{}{}
place.Image = strings.TrimPrefix(place.Image, s.domain)
}
if len(mapCodes) != len(story.Places) {
@@ -176,9 +181,11 @@ func (s *ScenarioService) updateStory(ctx context.Context, id int, story *storyt
cleanPlaces := make([]*storytelling.Place, 0, len(story.Places))
for _, place := range story.Places {
if place.Code == "" {
code := s.cleaner.ClearCode(place.Code)
if code == "" {
continue
}
place.Code = code
cleanPlaces = append(cleanPlaces, place)
}
story.Places = cleanPlaces