add games operations

This commit is contained in:
2026-07-26 22:38:33 +07:00
parent cf32a6114f
commit 6fc19833d3
24 changed files with 2430 additions and 96 deletions
+32
View File
@@ -0,0 +1,32 @@
package app
import (
"evening_detective_server/internal/services/game_service"
"evening_detective_server/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
func mapGame(
o *game_service.Game,
) *proto.Game {
return &proto.Game{
Id: int32(o.ID),
Name: o.Name,
Description: o.Description,
StartAt: timestamppb.New(o.StartAt),
Status: o.Status,
Scenario: mapScenario(o.Scenario),
Teams: mapTeams(o.Teams),
}
}
func mapGames(
o []*game_service.Game,
) []*proto.Game {
res := make([]*proto.Game, 0, len(o))
for _, item := range o {
res = append(res, mapGame(item))
}
return res
}
+115 -4
View File
@@ -6,6 +6,7 @@ import (
"evening_detective_server/internal/modules/processor_jwt"
"evening_detective_server/internal/modules/roles"
"evening_detective_server/internal/services/file_service"
"evening_detective_server/internal/services/game_service"
"evening_detective_server/internal/services/scenarios_service"
"evening_detective_server/internal/services/ui_service"
"evening_detective_server/internal/services/users_service"
@@ -23,6 +24,7 @@ type server struct {
uiService *ui_service.UiService
fileService *file_service.FileService
scenarioService *scenarios_service.ScenarioService
gameService *game_service.GameService
}
func NewServer(
@@ -30,12 +32,14 @@ func NewServer(
uiService *ui_service.UiService,
fileService *file_service.FileService,
scenarioService *scenarios_service.ScenarioService,
gameService *game_service.GameService,
) proto.EveningDetectiveServerServer {
return &server{
usersService: usersService,
uiService: uiService,
fileService: fileService,
scenarioService: scenarioService,
gameService: gameService,
}
}
@@ -167,10 +171,7 @@ func (s *server) DeleteUserRole(ctx context.Context, req *proto.DeleteUserRoleRe
return &proto.DeleteUserRoleRsp{}, nil
}
func (s *server) GetPermissions(
ctx context.Context,
req *proto.GetPermissionsReq,
) (*proto.GetPermissionsRsp, error) {
func (s *server) GetPermissions(ctx context.Context, req *proto.GetPermissionsReq) (*proto.GetPermissionsRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
permissions := s.uiService.GetPermissions(claims.Roles)
return &proto.GetPermissionsRsp{
@@ -427,3 +428,113 @@ func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScena
return &proto.DeleteScenarioPlaceRsp{}, nil
}
func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.AddGameRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Organizer) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if req.StartAt == nil {
return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil")
}
id, err := s.gameService.AddGame(
ctx,
req.Name,
req.Description,
req.StartAt.AsTime(),
req.ScenarioId,
)
if err != nil {
return &proto.AddGameRsp{
Error: err.Error(),
}, nil
}
return &proto.AddGameRsp{
Id: int32(id),
}, nil
}
func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*proto.DeleteGameRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Organizer) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
err := s.gameService.DeleteGame(
ctx,
int(req.Id),
)
if err != nil {
return &proto.DeleteGameRsp{
Error: err.Error(),
}, nil
}
return &proto.DeleteGameRsp{}, nil
}
func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Organizer) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
game, err := s.gameService.GetGame(ctx, int(req.Id))
if err != nil {
return &proto.GetGameRsp{
Error: err.Error(),
}, nil
}
return &proto.GetGameRsp{
Game: mapGame(game),
}, nil
}
func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.GetGamesRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Organizer) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
games, err := s.gameService.GetGames(ctx)
if err != nil {
return &proto.GetGamesRsp{
Error: err.Error(),
}, nil
}
return &proto.GetGamesRsp{
Games: mapGames(games),
}, nil
}
func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*proto.UpdateGameRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Organizer) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if req.StartAt == nil {
return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil")
}
err := s.gameService.UpdateGame(
ctx,
int(req.Id),
req.Name,
req.Description,
req.StartAt.AsTime(),
req.ScenarioId,
)
if err != nil {
return &proto.UpdateGameRsp{
Error: err.Error(),
}, nil
}
return &proto.UpdateGameRsp{}, nil
}
+3
View File
@@ -18,6 +18,9 @@ func mapScenarios(o []*scenarios_service.Scenario) []*proto.Scenario {
}
func mapScenario(o *scenarios_service.Scenario) *proto.Scenario {
if o == nil {
return nil
}
var publishedAt *timestamppb.Timestamp
if o.PublishedAt != nil {
publishedAt = timestamppb.New(*o.PublishedAt)
+25
View File
@@ -0,0 +1,25 @@
package app
import (
"evening_detective_server/internal/services/game_service"
"evening_detective_server/proto"
)
func mapTeam(
o *game_service.Team,
) *proto.Team {
return &proto.Team{
Id: int32(o.ID),
Name: o.Name,
}
}
func mapTeams(
o []*game_service.Team,
) []*proto.Team {
res := make([]*proto.Team, 0, len(o))
for _, item := range o {
res = append(res, mapTeam(item))
}
return res
}
@@ -20,6 +20,7 @@ func NewGenerator(length int) IPasswordGenerator {
}
func (g *generator) Generate() (string, error) {
return "1", nil
password := make([]byte, g.length)
for i := range password {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
+16
View File
@@ -0,0 +1,16 @@
package repos
import (
"time"
)
type Game struct {
ID int
Name string
Description string
StartAt time.Time
Status string
ScenarioId int
Scenario *Scenario
Teams []*Team
}
+179
View File
@@ -0,0 +1,179 @@
package games_repo
import (
"context"
"errors"
"evening_detective_server/internal/repos"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
ErrGameNotFound = errors.New("Игра не найдена")
)
type GamesRepo struct {
pool *pgxpool.Pool
}
func NewGamesRepo(pool *pgxpool.Pool) *GamesRepo {
return &GamesRepo{
pool: pool,
}
}
func (r *GamesRepo) AddGame(
ctx context.Context,
name string,
description string,
startAt time.Time,
scenarioId int32,
) (int, error) {
id := 0
err := r.pool.QueryRow(
ctx,
`INSERT INTO games (name, description, start_at, scenario_id)
VALUES ($1, $2, $3, $4)
RETURNING id`,
name,
description,
startAt,
scenarioId,
).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
func (r *GamesRepo) GetGameByID(
ctx context.Context,
id int,
) (*repos.Game, error) {
game := &repos.Game{}
row := r.pool.QueryRow(
ctx,
`SELECT
games.id,
games.name,
games.description,
games.start_at,
games.status,
games.scenario_id
FROM games
WHERE id = $1`,
id,
)
err := row.Scan(
&game.ID,
&game.Name,
&game.Description,
&game.StartAt,
&game.Status,
&game.ScenarioId,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrGameNotFound
}
return nil, err
}
return game, nil
}
func (r *GamesRepo) DeleteGameByID(
ctx context.Context,
id int,
) error {
tag, err := r.pool.Exec(
ctx,
`DELETE FROM games
WHERE id = $1`,
id,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrGameNotFound
}
return nil
}
func (r *GamesRepo) GetGames(
ctx context.Context,
) ([]*repos.Game, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
games.id,
games.name,
games.description,
games.start_at,
games.status
FROM games
ORDER BY created_at DESC`,
)
if err != nil {
return nil, err
}
defer rows.Close()
var games []*repos.Game
for rows.Next() {
game := &repos.Game{}
err := rows.Scan(
&game.ID,
&game.Name,
&game.Description,
&game.StartAt,
&game.Status,
)
if err != nil {
return nil, err
}
games = append(games, game)
}
if err := rows.Err(); err != nil {
return nil, err
}
return games, nil
}
func (r *GamesRepo) UpdateGame(
ctx context.Context,
id int,
name string,
description string,
startAt time.Time,
scenarioId int32,
) error {
tag, err := r.pool.Exec(
ctx,
`UPDATE games
SET
name = $1,
description = $2,
start_at = $3,
scenario_id = $4,
updated_at = NOW()
WHERE id = $5`,
name,
description,
startAt,
scenarioId,
id,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrGameNotFound
}
return nil
}
+18 -18
View File
@@ -23,13 +23,13 @@ func NewScenariosRepo(pool *pgxpool.Pool) *ScenariosRepo {
}
}
func (s *ScenariosRepo) AddScenario(
func (r *ScenariosRepo) AddScenario(
ctx context.Context,
name string,
authorId int,
) (int, error) {
id := 0
err := s.pool.QueryRow(
err := r.pool.QueryRow(
ctx,
`INSERT INTO scenarios (name, author_id)
VALUES ($1, $2)
@@ -44,11 +44,11 @@ func (s *ScenariosRepo) AddScenario(
return id, nil
}
func (s *ScenariosRepo) GetScenariosByAuthorID(
func (r *ScenariosRepo) GetScenariosByAuthorID(
ctx context.Context,
authorId int,
) ([]*repos.Scenario, error) {
rows, err := s.pool.Query(
rows, err := r.pool.Query(
ctx,
`SELECT
scenarios.id,
@@ -103,11 +103,11 @@ func (s *ScenariosRepo) GetScenariosByAuthorID(
return scenarios, nil
}
func (s *ScenariosRepo) GetScenariosByStatus(
func (r *ScenariosRepo) GetScenariosByStatus(
ctx context.Context,
status string,
) ([]*repos.Scenario, error) {
rows, err := s.pool.Query(
rows, err := r.pool.Query(
ctx,
`SELECT
scenarios.id,
@@ -162,14 +162,14 @@ func (s *ScenariosRepo) GetScenariosByStatus(
return scenarios, nil
}
func (s *ScenariosRepo) GetScenarioByID(
func (r *ScenariosRepo) GetScenarioByID(
ctx context.Context,
id int,
) (*repos.Scenario, error) {
scenario := &repos.Scenario{
Author: &repos.User{},
}
row := s.pool.QueryRow(
row := r.pool.QueryRow(
ctx,
`SELECT
scenarios.id,
@@ -213,14 +213,14 @@ func (s *ScenariosRepo) GetScenarioByID(
return scenario, nil
}
func (s *ScenariosRepo) UpdateScenarioByID(
func (r *ScenariosRepo) UpdateScenarioByID(
ctx context.Context,
id int,
name string,
description string,
image string,
) error {
tag, err := s.pool.Exec(
tag, err := r.pool.Exec(
ctx,
`UPDATE scenarios
SET
@@ -243,12 +243,12 @@ func (s *ScenariosRepo) UpdateScenarioByID(
return nil
}
func (s *ScenariosRepo) UpdateScenarioStatusByID(
func (r *ScenariosRepo) UpdateScenarioStatusByID(
ctx context.Context,
id int,
status string,
) error {
tag, err := s.pool.Exec(
tag, err := r.pool.Exec(
ctx,
`UPDATE scenarios
SET
@@ -268,11 +268,11 @@ func (s *ScenariosRepo) UpdateScenarioStatusByID(
return nil
}
func (s *ScenariosRepo) DeleteScenarioByID(
func (r *ScenariosRepo) DeleteScenarioByID(
ctx context.Context,
id int,
) error {
tag, err := s.pool.Exec(
tag, err := r.pool.Exec(
ctx,
`UPDATE scenarios
SET
@@ -289,12 +289,12 @@ func (s *ScenariosRepo) DeleteScenarioByID(
return nil
}
func (s *ScenariosRepo) GetStoryByScenarioID(
func (r *ScenariosRepo) GetStoryByScenarioID(
ctx context.Context,
id int,
) (string, error) {
story := ""
row := s.pool.QueryRow(
row := r.pool.QueryRow(
ctx,
`SELECT
scenario
@@ -315,12 +315,12 @@ func (s *ScenariosRepo) GetStoryByScenarioID(
return story, nil
}
func (s *ScenariosRepo) UpdateStoryByScenarioID(
func (r *ScenariosRepo) UpdateStoryByScenarioID(
ctx context.Context,
id int,
story string,
) error {
tag, err := s.pool.Exec(
tag, err := r.pool.Exec(
ctx,
`UPDATE scenarios
SET
+7
View File
@@ -0,0 +1,7 @@
package repos
type Team struct {
ID int
Name string
Password string
}
+67
View File
@@ -0,0 +1,67 @@
package teams_repo
import (
"context"
"evening_detective_server/internal/repos"
"github.com/jackc/pgx/v5/pgxpool"
)
type TeamsRepo struct {
pool *pgxpool.Pool
}
func NewTeamsRepo(pool *pgxpool.Pool) *TeamsRepo {
return &TeamsRepo{
pool: pool,
}
}
func (r *TeamsRepo) GetTeamsByGameID(
ctx context.Context,
gameId int,
) ([]*repos.Team, error) {
rows, err := r.pool.Query(
ctx,
`SELECT
scenarios.id,
scenarios.name,
scenarios.description,
scenarios.image,
scenarios.scenario,
users.id,
users.username,
scenarios.status,
scenarios.updated_at,
scenarios.created_at,
scenarios.published_at
FROM scenarios
LEFT JOIN users on scenarios.author_id = users.id
WHERE scenarios.author_id = $1 and is_deleted = FALSE
ORDER BY created_at DESC`,
gameId,
)
if err != nil {
return nil, err
}
defer rows.Close()
var teams []*repos.Team
for rows.Next() {
team := &repos.Team{}
err := rows.Scan(
&team.ID,
&team.Name,
&team.Password,
)
if err != nil {
return nil, err
}
teams = append(teams, team)
}
if err := rows.Err(); err != nil {
return nil, err
}
return teams, nil
}
+16
View File
@@ -0,0 +1,16 @@
package game_service
import (
"evening_detective_server/internal/services/scenarios_service"
"time"
)
type Game struct {
ID int
Name string
Description string
StartAt time.Time
Status string
Scenario *scenarios_service.Scenario
Teams []*Team
}
@@ -0,0 +1,32 @@
package game_service
import (
"evening_detective_server/internal/repos"
"evening_detective_server/internal/services/scenarios_service"
)
func mapGame(
o *repos.Game,
domain string,
) *Game {
return &Game{
ID: o.ID,
Name: o.Name,
Description: o.Description,
StartAt: o.StartAt,
Status: o.Status,
Scenario: scenarios_service.MapScenarioLight(o.Scenario, domain),
Teams: mapTeams(o.Teams),
}
}
func mapGames(
o []*repos.Game,
domain string,
) []*Game {
res := make([]*Game, 0, len(o))
for _, item := range o {
res = append(res, mapGame(item, domain))
}
return res
}
+93
View File
@@ -0,0 +1,93 @@
package game_service
import (
"context"
"evening_detective_server/internal/repos/games_repo"
"evening_detective_server/internal/repos/scenarios_repo"
"evening_detective_server/internal/repos/teams_repo"
"time"
)
type GameService struct {
gamesRepo *games_repo.GamesRepo
teamsRepo *teams_repo.TeamsRepo
scenariosRepo *scenarios_repo.ScenariosRepo
domain string
}
func NewGameService(
gamesRepo *games_repo.GamesRepo,
teamsRepo *teams_repo.TeamsRepo,
scenariosRepo *scenarios_repo.ScenariosRepo,
domain string,
) *GameService {
return &GameService{
gamesRepo: gamesRepo,
teamsRepo: teamsRepo,
scenariosRepo: scenariosRepo,
domain: domain,
}
}
func (s *GameService) AddGame(
ctx context.Context,
name string,
description string,
startAt time.Time,
scenarioId int32,
) (int, error) {
id, err := s.gamesRepo.AddGame(ctx, name, description, startAt, scenarioId)
if err != nil {
return 0, err
}
return id, nil
}
func (s *GameService) GetGame(
ctx context.Context,
id int,
) (*Game, error) {
game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil {
return nil, err
}
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, game.ScenarioId)
if err != nil {
return nil, err
}
game.Scenario = scenario
teams, err := s.teamsRepo.GetTeamsByGameID(ctx, id)
if err != nil {
return nil, err
}
game.Teams = teams
return mapGame(game, s.domain), nil
}
func (s *GameService) DeleteGame(
ctx context.Context,
id int,
) error {
return s.gamesRepo.DeleteGameByID(ctx, id)
}
func (s *GameService) GetGames(
ctx context.Context,
) ([]*Game, error) {
games, err := s.gamesRepo.GetGames(ctx)
if err != nil {
return nil, err
}
return mapGames(games, s.domain), nil
}
func (s *GameService) UpdateGame(
ctx context.Context,
id int,
name string,
description string,
startAt time.Time,
scenarioId int32,
) error {
return s.gamesRepo.UpdateGame(ctx, id, name, description, startAt, scenarioId)
}
+7
View File
@@ -0,0 +1,7 @@
package game_service
type Team struct {
ID int
Name string
Password string
}
@@ -0,0 +1,23 @@
package game_service
import "evening_detective_server/internal/repos"
func mapTeam(
o *repos.Team,
) *Team {
return &Team{
ID: o.ID,
Name: o.Name,
Password: o.Password,
}
}
func mapTeams(
o []*repos.Team,
) []*Team {
res := make([]*Team, 0, len(o))
for _, item := range o {
res = append(res, mapTeam(item))
}
return res
}
@@ -12,7 +12,7 @@ func mapScenariosLight(
) []*Scenario {
res := make([]*Scenario, 0, len(o))
for _, item := range o {
res = append(res, mapScenarioLight(item, domain))
res = append(res, MapScenarioLight(item, domain))
}
return res
}
@@ -32,10 +32,13 @@ func mapScenarios(
return res, nil
}
func mapScenarioLight(
func MapScenarioLight(
o *repos.Scenario,
domain string,
) *Scenario {
if o == nil {
return nil
}
return &Scenario{
ID: o.ID,
Name: o.Name,
@@ -55,7 +58,7 @@ func mapScenario(
o *repos.Scenario,
domain string,
) (*Scenario, error) {
scenario := mapScenarioLight(o, domain)
scenario := MapScenarioLight(o, domain)
story, err := mapStory(o.Scenario, domain)
if err != nil {
return nil, err
@@ -75,7 +75,7 @@ func (s *ScenarioService) GetScenarioByID(
if err != nil {
return nil, err
}
return mapScenarioLight(scenario, s.domain), nil
return MapScenarioLight(scenario, s.domain), nil
}
func (s *ScenarioService) UpdateScenarioByID(