diff --git a/experimental/2nub/src/server/routes.ts b/experimental/2nub/src/server/routes.ts index 95a2faf..31b4414 100644 --- a/experimental/2nub/src/server/routes.ts +++ b/experimental/2nub/src/server/routes.ts @@ -1,8 +1,30 @@ -import { Router } from 'express'; +import { Router, Response } from 'express'; import { gameStateManager } from './gameState'; import { broadcastToGame, broadcastToAll } from './websocket'; import { CreateGameRequest, JoinGameRequest, ApiResponse } from '../types'; +// TODO: consider not returning `game` state in the HTTP response, since websockets should handle real time server-to-client data updates + +function respondGameNotFound(res: Response, message: string | null) { + return respondFailure(res, 404, message ? `Game not found: ${message}` : 'Game not found'); +} + +function respondSuccessWithData(res: Response, status: number, data: T) { + const response: ApiResponse = { + success: true, + data, + }; + return res.status(status).json(response); +} + +function respondFailure(res: Response, status: number, message: string) { + const response: ApiResponse = { + success: false, + error: message, + }; + return res.status(status).json(response); +} + export function setupRoutes(): Router { const router = Router(); @@ -20,42 +42,26 @@ export function setupRoutes(): Router { const game = gameStateManager.getGame(gameId); if (!game) { - const response: ApiResponse = { - success: false, - error: 'Game not found' - }; - return res.status(404).json(response); + return respondGameNotFound(res, null); } - - const response: ApiResponse = { - success: true, - data: game - }; - res.json(response); + return respondSuccessWithData(res, 200, game); }); router.post('/games', (req, res) => { const { name }: CreateGameRequest = req.body; if (!name || name.trim().length === 0) { - const response: ApiResponse = { - success: false, - error: 'Invalid game parameters' - }; - return res.status(400).json(response); + return respondFailure(res, 400, 'Invalid game parameters'); } const game = gameStateManager.createGame(name.trim()); broadcastToAll('gameCreated', game); - const response: ApiResponse = { - success: true, - data: game - }; - res.status(201).json(response); + return respondSuccessWithData(res, 201, game); }); + // TODO: consider removing rejoining logic from here since /rejoin exists router.post('/games/:gameId/join', (req, res) => { const { gameId } = req.params; const { playerName, existingPlayerId }: { playerName?: string; existingPlayerId?: string } = req.body; @@ -63,21 +69,13 @@ export function setupRoutes(): Router { // Joining as existing disconnected player if (existingPlayerId) { if (!existingPlayerId.trim()) { - const response: ApiResponse = { - success: false, - error: 'Player ID is required' - }; - return res.status(400).json(response); + return respondFailure(res, 400, 'Player ID is required'); } const result = gameStateManager.rejoinPlayer(gameId, existingPlayerId.trim()); if (!result) { - const response: ApiResponse = { - success: false, - error: 'Game or player not found, or player is already connected' - }; - return res.status(404).json(response); + return respondFailure(res, 404, 'Game or player not found, or player is already connected'); } // Set player as connected @@ -88,30 +86,21 @@ export function setupRoutes(): Router { broadcastToGame(gameId, 'playerJoined', { player: result.player, game: updatedGame }); - const response: ApiResponse = { - success: true, - data: { player: result.player, game: updatedGame } - }; - return res.json(response); + return respondSuccessWithData(res, 200, { + player: result.player, + game: updatedGame + }); } // Joining as new player if (!playerName || playerName.trim() === '') { - const response: ApiResponse = { - success: false, - error: 'Player name is required when joining as new player' - }; - return res.status(400).json(response); + return respondFailure(res, 400, 'Player name is required when joining as new player'); } const player = gameStateManager.addPlayer(gameId, playerName.trim()); if (!player) { - const response: ApiResponse = { - success: false, - error: 'Game not found or full' - }; - return res.status(400).json(response); + return respondFailure(res, 400, 'Game not found or full'); } // Set player as connected @@ -121,11 +110,7 @@ export function setupRoutes(): Router { broadcastToGame(gameId, 'playerJoined', { player, game }); - const response: ApiResponse = { - success: true, - data: { player, game } - }; - res.status(201).json(response); + return respondSuccessWithData(res, 201, { player, game }); }); router.post('/games/:gameId/rejoin', (req, res) => { @@ -134,21 +119,13 @@ export function setupRoutes(): Router { const { playerId }: { playerId: string } = req.body; if (!playerId || playerId.trim() === '') { - const response: ApiResponse = { - success: false, - error: 'Player ID is required' - }; - return res.status(400).json(response); + return respondFailure(res, 400, 'Player ID is required'); } const result = gameStateManager.rejoinPlayer(gameId, playerId.trim()); if (!result) { - const response: ApiResponse = { - success: false, - error: 'Game or player not found' - }; - return res.status(404).json(response); + return respondFailure(res, 404, 'Game or player not found'); } // Set player as connected @@ -159,11 +136,7 @@ export function setupRoutes(): Router { broadcastToGame(gameId, 'playerJoined', { player: result.player, game: updatedGame }); - const response: ApiResponse = { - success: true, - data: { player: result.player, game: updatedGame } - }; - res.json(response); + return respondSuccessWithData(res, 200, { player: result.player, game: updatedGame }); }); router.post('/games/:gameId/players/:playerId/disconnect', (req, res) => { @@ -172,11 +145,7 @@ export function setupRoutes(): Router { const updated = gameStateManager.updatePlayerConnection(gameId, playerId, false); if (!updated) { - const response: ApiResponse = { - success: false, - error: 'Player or game not found' - }; - return res.status(404).json(response); + return respondFailure(res, 404, 'Player or game not found'); } const game = gameStateManager.getGame(gameId); @@ -185,11 +154,7 @@ export function setupRoutes(): Router { broadcastToGame(gameId, 'gameState', game); } - const response: ApiResponse = { - success: true, - data: { disconnected: true } - }; - res.json(response); + return respondSuccessWithData(res, 200, { disconnected: true }); }); router.delete('/games/:gameId/players/:playerId', (req, res) => { @@ -198,11 +163,7 @@ export function setupRoutes(): Router { const removed = gameStateManager.removePlayer(gameId, playerId); if (!removed) { - const response: ApiResponse = { - success: false, - error: 'Player or game not found' - }; - return res.status(404).json(response); + return respondFailure(res, 404, 'Player or game not found'); } const game = gameStateManager.getGame(gameId); @@ -213,11 +174,7 @@ export function setupRoutes(): Router { broadcastToAll('gameDeleted', { gameId }); } - const response: ApiResponse = { - success: true, - data: { removed: true } - }; - res.json(response); + return respondSuccessWithData(res, 200, { removed: true }); }); router.delete('/games/:gameId', (req, res) => { @@ -226,20 +183,12 @@ export function setupRoutes(): Router { const deleted = gameStateManager.deleteGame(gameId); if (!deleted) { - const response: ApiResponse = { - success: false, - error: 'Game not found' - }; - return res.status(404).json(response); + return respondGameNotFound(res, null); } broadcastToAll('gameDeleted', { gameId }); - const response: ApiResponse = { - success: true, - data: { deleted: true } - }; - res.json(response); + return respondSuccessWithData(res, 200, { deleted: true }); }); return router;