diff options
| -rw-r--r-- | backend/admin/handlers.go | 4 | ||||
| -rw-r--r-- | backend/admin/templates/game_edit.html | 13 | ||||
| -rw-r--r-- | backend/admin/templates/games.html | 2 | ||||
| -rw-r--r-- | backend/api/generated.go | 72 | ||||
| -rw-r--r-- | backend/api/handlers.go | 2 | ||||
| -rw-r--r-- | backend/db/models.go | 1 | ||||
| -rw-r--r-- | backend/db/query.sql.go | 71 | ||||
| -rw-r--r-- | backend/fixtures/dev.sql | 24 | ||||
| -rw-r--r-- | backend/game/http.go | 5 | ||||
| -rw-r--r-- | backend/game/hub.go | 10 | ||||
| -rw-r--r-- | backend/game/models.go | 6 | ||||
| -rw-r--r-- | backend/query.sql | 16 | ||||
| -rw-r--r-- | backend/schema.sql | 1 | ||||
| -rw-r--r-- | frontend/app/.server/api/schema.d.ts | 5 | ||||
| -rw-r--r-- | frontend/app/routes/dashboard.tsx | 3 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.watch.tsx | 5 | ||||
| -rw-r--r-- | openapi.yaml | 7 |
17 files changed, 190 insertions, 57 deletions
diff --git a/backend/admin/handlers.go b/backend/admin/handlers.go index 14523e6..d9a6977 100644 --- a/backend/admin/handlers.go +++ b/backend/admin/handlers.go @@ -130,6 +130,7 @@ func (h *AdminHandler) getGames(c echo.Context) error { } games[i] = echo.Map{ "GameID": g.GameID, + "GameType": g.GameType, "State": g.State, "DisplayName": g.DisplayName, "DurationSeconds": g.DurationSeconds, @@ -167,6 +168,7 @@ func (h *AdminHandler) getGameEdit(c echo.Context) error { "Title": "Game Edit", "Game": echo.Map{ "GameID": row.GameID, + "GameType": row.GameType, "State": row.State, "DisplayName": row.DisplayName, "DurationSeconds": row.DurationSeconds, @@ -190,6 +192,7 @@ func (h *AdminHandler) postGameEdit(c echo.Context) error { } } + gameType := c.FormValue("game_type") state := c.FormValue("state") displayName := c.FormValue("display_name") durationSeconds, err := strconv.Atoi(c.FormValue("duration_seconds")) @@ -247,6 +250,7 @@ func (h *AdminHandler) postGameEdit(c echo.Context) error { err = h.q.UpdateGame(c.Request().Context(), db.UpdateGameParams{ GameID: int32(gameID), + GameType: gameType, State: state, DisplayName: displayName, DurationSeconds: int32(durationSeconds), diff --git a/backend/admin/templates/game_edit.html b/backend/admin/templates/game_edit.html index 8bc5410..764b577 100644 --- a/backend/admin/templates/game_edit.html +++ b/backend/admin/templates/game_edit.html @@ -15,11 +15,18 @@ <input type="text" name="display_name" value="{{ .Game.DisplayName }}" required> </div> <div> + <label>Game Type</label> + <select name="game_type" required> + <option value="1v1"{{ if eq .Game.GameType "1v1" }} selected{{ end }}>1v1</option> + <option value="multiplayer"{{ if eq .Game.GameType "multiplayer" }} selected{{ end }}>Multiplayer</option> + </select> + </div> + <div> <label>State</label> - <select> + <select name="state" required> <option value="closed"{{ if eq .Game.State "closed" }} selected{{ end }}>Closed</option> - <option value="waiting_entries"{{ if eq .Game.State "waiting_entries" }} selected{{ end }}>WaitingEntries</option> - <option value="waiting_start"{{ if eq .Game.State "waiting_start" }} selected{{ end }}>WaitingStart</option> + <option value="waiting_entries"{{ if eq .Game.State "waiting_entries" }} selected{{ end }}>Waiting Entries</option> + <option value="waiting_start"{{ if eq .Game.State "waiting_start" }} selected{{ end }}>Waiting Start</option> <option value="prepare"{{ if eq .Game.State "prepare" }} selected{{ end }}>Prepare</option> <option value="starting"{{ if eq .Game.State "starting" }} selected{{ end }}>Starting</option> <option value="gaming"{{ if eq .Game.State "gaming" }} selected{{ end }}>Gaming</option> diff --git a/backend/admin/templates/games.html b/backend/admin/templates/games.html index 244fc94..47dc4a3 100644 --- a/backend/admin/templates/games.html +++ b/backend/admin/templates/games.html @@ -9,7 +9,7 @@ {{ range .Games }} <li> <a href="/admin/games/{{ .GameID }}"> - {{ .DisplayName }} (id={{ .GameID }}) + {{ .DisplayName }} (id={{ .GameID }} type={{ .GameType }} state={{ .State }}) </a> </li> {{ end }} diff --git a/backend/api/generated.go b/backend/api/generated.go index ea1c315..e371c7d 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -22,6 +22,12 @@ import ( strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo" ) +// Defines values for GameGameType. +const ( + Multiplayer GameGameType = "multiplayer" + N1V1 GameGameType = "1v1" +) + // Defines values for GameState. const ( Closed GameState = "closed" @@ -50,14 +56,18 @@ type Error struct { // Game defines model for Game. type Game struct { - DisplayName string `json:"display_name"` - DurationSeconds int `json:"duration_seconds"` - GameID int `json:"game_id"` - Problem *Problem `json:"problem,omitempty"` - StartedAt *int `json:"started_at,omitempty"` - State GameState `json:"state"` + DisplayName string `json:"display_name"` + DurationSeconds int `json:"duration_seconds"` + GameID int `json:"game_id"` + GameType GameGameType `json:"game_type"` + Problem *Problem `json:"problem,omitempty"` + StartedAt *int `json:"started_at,omitempty"` + State GameState `json:"state"` } +// GameGameType defines model for Game.GameType. +type GameGameType string + // GameState defines model for Game.State. type GameState string @@ -1091,31 +1101,31 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/9xZX2/bNhD/Kho3oBvA+V+CovNbmrVZh64z6hZ7KAKDls42M4lUSSqJF+i7DyRlybRo", - "S3aUrFgfCtvk3f149+Pd5fiAQp6knAFTEo0fUEoESUCBMN9WQCIQM5KpFRf0H6IoZ/p3ytC4WEQYMZIA", - "GqMLZxdGAr5mVECExkpkgJEMV5AQLa7WqRaQSlC2RHmOUUrUarYkCcxoVBrQP1bqN6stFFOmYAkC5Vq1", - "AJlyJsEc6DWJPsLXDKTS30LOFDDzkaRpTEMDvX8j7SkrvT8IWKAx+r5fOatvV2X/jRC8MBWBDAVNrZe0", - "rUAUxnKM3nIxp1EE7OktV6ZyjD5w9ZZnLHp6sx+4ChbGVI7RZ7ZhDTyDaceaXi4ktEIrpLkteApCUUuF", - "BKQkS9Af4Z4kaayZ847dkphWccMerlb0+1IquS438vkNhCbgV4a3u2YjKtOYrGesWK1s6/3BsG4SoygT", - "xlszCSFnkXTkzl4OcI34GG1dpnLr0LcxFXweQ9Lk+kmxTftWEaEgmhHlaP/l/OXLV+evBl44UhFlz8uy", - "RHsujLkEfZvvCFWULWfAlNA+qn4xdpBGCCkRgArL2inmfPbDgjIqVxDpGFTOLNUfjl+VVCxA7MbH4/p9", - "kZ7EZA3ij4pUnMGfCzT+ctitNdHp6BLl+Eihy9EU5dc+JHrldDCXo+kbpsT6JEQfgUSnSV7yCE4SnGbz", - "hKr9rjCK61eSqMbMs0/bhKxjTkzGs7QwmU6XF5Sa7eNwJMehttvERbOKLZpWLNuBUDtXWJy2uhWpoEz9", - "+OI3iGOOgzsu4ui7Fz81IjOK2kKyhKmBOeAdMBKt3NMWhOXeMSCEkegURMHGzvhm9bVjnLS2n4JzLoxv", - "gXU6Zz4q406KCnN0zpmOLqemSp0i+eYewo8gs3hfxnL3dMMjR2czl+QoHMM9hMJi6JxPXji1k8qQC5dU", - "Q91msCyOyVx/tX8I+NuOTG73HTILQ5DS7RY2PzYdr1CHC0BtT7ihV2cRLBS2C1/VPnUfux0gtQMe21zu", - "QNqIt4Vj72Jnbjbq2jl506t272IHRP1m6NUjOvE6oa34PjR/ERWuTuxrXVnT2F571R6dv2vi7ZNwTbR1", - "s1mT9OVvv/qTGelVd4CRd3a/oWRnTedBEB3Wf1xcqBZ/tu7miVIOH24bDsWwuyC1KrDboeq4wrYAVM/U", - "bV2P/7tqrDVEIIRLrz37eOYmReTwr9HP25TaKful+hJP60A8skD59bUkWXcl6jCMZ61Rk6rB2HHp9nxw", - "mwafVlQGVAYk2HQXvkRkl1pdB0VVvJPxClS+aZ6/w7E8s5rc2abv0J8liGMmi7/zFQt+5eA7KQ05m5lJ", - "uyPSpwlZguzf8BXr3aRLr6ickSihrn8XJJbV5Z9zHgMxc+hMetLL6MznUb21fgoNpdGfGytbSmozvRJ3", - "3bdaHWULboYFNq7oIp4TJbiUgYYoGImDO5gHF5N3CKNbENKOoAe9YW+g0fMUGEkpGqOz3qA3QPZ1w4So", - "vySJDdYSzHXQ8TPzxXcRGqMrUFdmA3beYfY0RNWWvvedJr/eefwYDQZHTeJdepXQqYJEtslWVUJCRAiy", - "9k5f5Z4ouPP991SqgC8CK5FjdD4Y7oNQnrnvvgpoobNmoa3HE11IsiQhYr2BUNjPcRHK/kMxQc6bgtpR", - "THGjnPOU9gQcaBd5T6RbBfrCuPjZIqwlzpslyjc0lxJXoAJSANaUiPnSZsOUSw8TJlyq92aLdQ5I9Zrb", - "MeWJ8UiJlHdcRDv9dvHrcHTmS9uPzK5sQ+bCtD+q7htt3ikLFf8bdor6vf7X2/q/uc8xStpQcmrb0UUW", - "x+tA0w2Y0lA3jDuapg6HdC0PLHEMh8rD7Usmn8yGb7FC/K/iYu+2XHGhfo7pLUQBMeYCCzDP8/zfAAAA", - "//9iv6ZqPCEAAA==", + "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", + "5c8RuYXQw9qcZwBmWZb9GwAA//9z5sw+kyEAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handlers.go b/backend/api/handlers.go index a824f17..659e5c1 100644 --- a/backend/api/handlers.go +++ b/backend/api/handlers.go @@ -87,6 +87,7 @@ func (h *ApiHandler) GetGames(ctx context.Context, request GetGamesRequestObject } games[i] = Game{ GameID: int(row.GameID), + GameType: GameGameType(row.GameType), State: GameState(row.State), DisplayName: row.DisplayName, DurationSeconds: int(row.DurationSeconds), @@ -134,6 +135,7 @@ func (h *ApiHandler) GetGame(ctx context.Context, request GetGameRequestObject, } game := Game{ GameID: int(row.GameID), + GameType: GameGameType(row.GameType), State: GameState(row.State), DisplayName: row.DisplayName, DurationSeconds: int(row.DurationSeconds), diff --git a/backend/db/models.go b/backend/db/models.go index 5ad4b6b..51157bc 100644 --- a/backend/db/models.go +++ b/backend/db/models.go @@ -10,6 +10,7 @@ import ( type Game struct { GameID int32 + GameType string State string DisplayName string DurationSeconds int32 diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go index 074e767..8df3bf5 100644 --- a/backend/db/query.sql.go +++ b/backend/db/query.sql.go @@ -12,7 +12,7 @@ import ( ) const getGameByID = `-- name: GetGameByID :one -SELECT game_id, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description FROM games +SELECT game_id, game_type, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description FROM games LEFT JOIN problems ON games.problem_id = problems.problem_id WHERE games.game_id = $1 LIMIT 1 @@ -20,6 +20,7 @@ LIMIT 1 type GetGameByIDRow struct { GameID int32 + GameType string State string DisplayName string DurationSeconds int32 @@ -36,6 +37,7 @@ func (q *Queries) GetGameByID(ctx context.Context, gameID int32) (GetGameByIDRow var i GetGameByIDRow err := row.Scan( &i.GameID, + &i.GameType, &i.State, &i.DisplayName, &i.DurationSeconds, @@ -107,13 +109,60 @@ func (q *Queries) GetUserByID(ctx context.Context, userID int32) (User, error) { return i, err } +const listGamePlayers = `-- name: ListGamePlayers :many +SELECT game_id, game_players.user_id, users.user_id, username, display_name, icon_path, is_admin, created_at FROM game_players +LEFT JOIN users ON game_players.user_id = users.user_id +WHERE game_players.game_id = $1 +` + +type ListGamePlayersRow struct { + GameID int32 + UserID int32 + UserID_2 *int32 + Username *string + DisplayName *string + IconPath *string + IsAdmin *bool + CreatedAt pgtype.Timestamp +} + +func (q *Queries) ListGamePlayers(ctx context.Context, gameID int32) ([]ListGamePlayersRow, error) { + rows, err := q.db.Query(ctx, listGamePlayers, gameID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListGamePlayersRow + for rows.Next() { + var i ListGamePlayersRow + if err := rows.Scan( + &i.GameID, + &i.UserID, + &i.UserID_2, + &i.Username, + &i.DisplayName, + &i.IconPath, + &i.IsAdmin, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listGames = `-- name: ListGames :many -SELECT game_id, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description FROM games +SELECT game_id, game_type, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description FROM games LEFT JOIN problems ON games.problem_id = problems.problem_id ` type ListGamesRow struct { GameID int32 + GameType string State string DisplayName string DurationSeconds int32 @@ -136,6 +185,7 @@ func (q *Queries) ListGames(ctx context.Context) ([]ListGamesRow, error) { var i ListGamesRow if err := rows.Scan( &i.GameID, + &i.GameType, &i.State, &i.DisplayName, &i.DurationSeconds, @@ -157,7 +207,7 @@ func (q *Queries) ListGames(ctx context.Context) ([]ListGamesRow, error) { } const listGamesForPlayer = `-- name: ListGamesForPlayer :many -SELECT games.game_id, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description, game_players.game_id, user_id FROM games +SELECT games.game_id, game_type, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description, game_players.game_id, user_id FROM games LEFT JOIN problems ON games.problem_id = problems.problem_id JOIN game_players ON games.game_id = game_players.game_id WHERE game_players.user_id = $1 @@ -165,6 +215,7 @@ WHERE game_players.user_id = $1 type ListGamesForPlayerRow struct { GameID int32 + GameType string State string DisplayName string DurationSeconds int32 @@ -189,6 +240,7 @@ func (q *Queries) ListGamesForPlayer(ctx context.Context, userID int32) ([]ListG var i ListGamesForPlayerRow if err := rows.Scan( &i.GameID, + &i.GameType, &i.State, &i.DisplayName, &i.DurationSeconds, @@ -245,16 +297,18 @@ func (q *Queries) ListUsers(ctx context.Context) ([]User, error) { const updateGame = `-- name: UpdateGame :exec UPDATE games SET - state = $2, - display_name = $3, - duration_seconds = $4, - started_at = $5, - problem_id = $6 + game_type = $2, + state = $3, + display_name = $4, + duration_seconds = $5, + started_at = $6, + problem_id = $7 WHERE game_id = $1 ` type UpdateGameParams struct { GameID int32 + GameType string State string DisplayName string DurationSeconds int32 @@ -265,6 +319,7 @@ type UpdateGameParams struct { func (q *Queries) UpdateGame(ctx context.Context, arg UpdateGameParams) error { _, err := q.db.Exec(ctx, updateGame, arg.GameID, + arg.GameType, arg.State, arg.DisplayName, arg.DurationSeconds, diff --git a/backend/fixtures/dev.sql b/backend/fixtures/dev.sql index 5e47386..a468d01 100644 --- a/backend/fixtures/dev.sql +++ b/backend/fixtures/dev.sql @@ -17,14 +17,20 @@ INSERT INTO problems VALUES ('TEST problem 1', 'This is TEST problem 1'), ('TEST problem 2', 'This is TEST problem 2'), - ('TEST problem 3', 'This is TEST problem 3'); + ('TEST problem 3', 'This is TEST problem 3'), + ('TEST problem 4', 'This is TEST problem 4'), + ('TEST problem 5', 'This is TEST problem 5'), + ('TEST problem 6', 'This is TEST problem 6'); INSERT INTO games -(state, display_name, duration_seconds, problem_id) +(game_type, state, display_name, duration_seconds, problem_id) VALUES - ('waiting_entries', 'TEST game 1', 180, 1), - ('closed', 'TEST game 2', 180, 2), - ('finished', 'TEST game 3', 180, 3); + ('1v1', 'waiting_entries', 'TEST game 1', 180, 1), + ('1v1', 'closed', 'TEST game 2', 180, 2), + ('1v1', 'finished', 'TEST game 3', 180, 3), + ('multiplayer', 'waiting_start', 'TEST game 4', 180, 4), + ('multiplayer', 'closed', 'TEST game 5', 180, 5), + ('multiplayer', 'finished', 'TEST game 6', 180, 6); INSERT INTO game_players (game_id, user_id) @@ -34,4 +40,10 @@ VALUES (2, 1), (2, 2), (3, 1), - (3, 2); + (3, 2), + (4, 1), + (4, 2), + (5, 1), + (5, 2), + (6, 1), + (6, 2); diff --git a/backend/game/http.go b/backend/game/http.go index f9036c5..865c724 100644 --- a/backend/game/http.go +++ b/backend/game/http.go @@ -56,5 +56,10 @@ func (h *sockHandler) HandleSockGolfWatch(c echo.Context) error { if hub == nil { return echo.NewHTTPError(http.StatusNotFound, "Game not found") } + + if hub.game.gameType != gameType1v1 { + return echo.NewHTTPError(http.StatusBadRequest, "Only 1v1 game is supported") + } + return serveWatcherWs(hub, c.Response(), c.Request()) } diff --git a/backend/game/hub.go b/backend/game/hub.go index 239f5da..70bf71f 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -81,7 +81,7 @@ func (hub *gameHub) run() { entriedPlayerCount++ } } - if entriedPlayerCount == 2 { + if entriedPlayerCount == hub.game.playerCount { err := hub.q.UpdateGameState(hub.ctx, db.UpdateGameStateParams{ GameID: int32(hub.game.gameID), State: string(gameStateWaitingStart), @@ -101,7 +101,7 @@ func (hub *gameHub) run() { readyPlayerCount++ } } - if readyPlayerCount == 2 { + if readyPlayerCount == hub.game.playerCount { startAt := time.Now().Add(11 * time.Second).UTC() for player := range hub.players { player.s2cMessages <- &playerMessageS2CStart{ @@ -300,6 +300,11 @@ func (hubs *GameHubs) RestoreFromDB(ctx context.Context) error { description: *row.Description, } } + // TODO: N+1 + playerRows, err := hubs.q.ListGamePlayers(ctx, int32(row.GameID)) + if err != nil { + return err + } hubs.hubs[int(row.GameID)] = newGameHub(ctx, &game{ gameID: int(row.GameID), durationSeconds: int(row.DurationSeconds), @@ -307,6 +312,7 @@ func (hubs *GameHubs) RestoreFromDB(ctx context.Context) error { displayName: row.DisplayName, startedAt: startedAt, problem: problem_, + playerCount: len(playerRows), }, hubs.q) } return nil diff --git a/backend/game/models.go b/backend/game/models.go index 6c299d6..4080482 100644 --- a/backend/game/models.go +++ b/backend/game/models.go @@ -6,9 +6,13 @@ import ( "github.com/nsfisis/iosdc-japan-2024-albatross/backend/api" ) +type gameType = api.GameGameType type gameState = api.GameState const ( + gameType1v1 = api.N1V1 + gameTypeMultiplayer = api.Multiplayer + gameStateClosed gameState = api.Closed gameStateWaitingEntries gameState = api.WaitingEntries gameStateWaitingStart gameState = api.WaitingStart @@ -20,11 +24,13 @@ const ( type game struct { gameID int + gameType gameType state gameState displayName string durationSeconds int startedAt *time.Time problem *problem + playerCount int } type problem struct { diff --git a/backend/query.sql b/backend/query.sql index 886f96c..245d5cf 100644 --- a/backend/query.sql +++ b/backend/query.sql @@ -38,12 +38,18 @@ LEFT JOIN problems ON games.problem_id = problems.problem_id WHERE games.game_id = $1 LIMIT 1; +-- name: ListGamePlayers :many +SELECT * FROM game_players +LEFT JOIN users ON game_players.user_id = users.user_id +WHERE game_players.game_id = $1; + -- name: UpdateGame :exec UPDATE games SET - state = $2, - display_name = $3, - duration_seconds = $4, - started_at = $5, - problem_id = $6 + game_type = $2, + state = $3, + display_name = $4, + duration_seconds = $5, + started_at = $6, + problem_id = $7 WHERE game_id = $1; diff --git a/backend/schema.sql b/backend/schema.sql index 4c63511..4a1d4b0 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -25,6 +25,7 @@ CREATE TABLE problems ( CREATE TABLE games ( game_id SERIAL PRIMARY KEY, + game_type VARCHAR(16) NOT NULL, state VARCHAR(32) NOT NULL, display_name VARCHAR(255) NOT NULL, duration_seconds INT NOT NULL, diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index 88067a8..62badcf 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -96,6 +96,11 @@ export interface components { /** @example 1 */ game_id: number; /** + * @example 1v1 + * @enum {string} + */ + game_type: "1v1" | "multiplayer"; + /** * @example closed * @enum {string} */ diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx index e23d7aa..e6a43de 100644 --- a/frontend/app/routes/dashboard.tsx +++ b/frontend/app/routes/dashboard.tsx @@ -42,7 +42,8 @@ export default function Dashboard() { <ul className="list-disc list-inside"> {games.map((game) => ( <li key={game.game_id}> - {game.display_name}{" "} + {game.display_name} + {game.game_type === "multiplayer" ? " (Multiplayer)" : " (1v1)"} {game.state === "closed" || game.state === "finished" ? ( <span className="inline-block px-6 py-2 text-gray-400 bg-gray-200 cursor-not-allowed rounded"> Entry diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx index 83b7a1a..5edf92f 100644 --- a/frontend/app/routes/golf.$gameId.watch.tsx +++ b/frontend/app/routes/golf.$gameId.watch.tsx @@ -25,6 +25,11 @@ export async function loader({ params, request }: LoaderFunctionArgs) { }; const [game, sockToken] = await Promise.all([fetchGame(), fetchSockToken()]); + + if (game.game_type !== "1v1") { + return new Response("Not Found", { status: 404 }); + } + return { game, sockToken, diff --git a/openapi.yaml b/openapi.yaml index 2c91ad1..2e18728 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -185,6 +185,12 @@ components: game_id: type: integer example: 1 + game_type: + type: string + example: "1v1" + enum: + - 1v1 + - multiplayer state: type: string example: "closed" @@ -209,6 +215,7 @@ components: $ref: '#/components/schemas/Problem' required: - game_id + - game_type - state - display_name - duration_seconds |
