add end game endpoint

This commit is contained in:
2025-09-14 17:20:23 +02:00
parent 0e0a127c60
commit 5a97c9d0c1
2 changed files with 100 additions and 1 deletions

View File

@@ -41,12 +41,17 @@ const turnSchema = Joi.object({
end_points: Joi.number().integer().min(0).required() end_points: Joi.number().integer().min(0).required()
}); });
const endGameSchema = Joi.object({
winner: Joi.number().integer().positive()
});
const schemasByPath = { const schemasByPath = {
"/api/users": userSchema, "/api/users": userSchema,
"/api/games": createGameSchema, "/api/games": createGameSchema,
"/api/games/:id/players": addPlayerSchema, "/api/games/:id/players": addPlayerSchema,
"/api/games/:id/lock": lockGameSchema, "/api/games/:id/lock": lockGameSchema,
"/api/games/:id/turns": turnSchema "/api/games/:id/turns": turnSchema,
"/api/games/:id/end": endGameSchema,
}; };
export function generateRequestBodies() { export function generateRequestBodies() {
@@ -402,6 +407,66 @@ router.patch("/games/:id/lock", asyncHandler(async (req, res) => {
} }
})); }));
/**
* @swagger
* /api/games/{id}/end:
* patch:
* summary: End a game
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Game marked as finished
* content:
* application/json:
* example:
* message: "Game finished"
* gameId: 1
*/
router.patch("/games/:id/end", asyncHandler(async (req,res) => {
const gameId = parseInt(req.params.id, 10);
if (isNaN(gameId)) throw new ApiError(400, "Invalid game ID");
const { error, value } = endGameSchema.validate(req.body);
if (error) throw new ApiError(400, error.details[0].message);
const { winner } = value;
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
const [gameRows] = await conn.query("SELECT * FROM games WHERE id = ?", [gameId]);
if (!gameRows.length) throw new ApiError(404, "Game not found");
if (gameRows[0].is_finished) throw new ApiError(400, "Game is already finished");
if (winner) {
const [playerRows] = await conn.query(
"SELECT 1 FROM game_players WHERE game = ? AND user = ?",
[gameId, winner]
);
if (!playerRows.length) throw new ApiError(400, "Winner must be a player in the game");
}
await conn.query(
"UPDATE games SET is_finished = TRUE, winner = ? WHERE id = ?",
[winner || null, gameId]
);
await conn.commit();
res.json({ message: "Game finished", gameId });
} catch (err) {
await conn.rollback();
throw new ApiError(500, "Failed to finish game", err);
} finally {
conn.release();
}
}));
/** /**
* @swagger * @swagger
* /api/games/{id}/players: * /api/games/{id}/players:

View File

@@ -94,6 +94,40 @@ const migrations = [
ADD CONSTRAINT uniq_game_user UNIQUE (game, user); ADD CONSTRAINT uniq_game_user UNIQUE (game, user);
`); `);
} }
},
{
name: "merge_current_games_into_games",
up: async (conn) => {
await conn.query(`
ALTER TABLE game_players
DROP FOREIGN KEY game_players_ibfk_1;
`);
await conn.query(`
ALTER TABLE turns
DROP FOREIGN KEY turns_ibfk_1;
`);
await conn.query(`DROP TABLE IF EXISTS games;`);
await conn.query(`ALTER TABLE current_games RENAME TO games;`);
await conn.query(`
ALTER TABLE games
ADD COLUMN is_finished BOOL DEFAULT FALSE AFTER is_local,
ADD COLUMN winner INT DEFAULT NULL AFTER is_finished,
ADD CONSTRAINT fk_games_winner FOREIGN KEY (winner) REFERENCES users(id);
`);
await conn.query(`
ALTER TABLE game_players
ADD CONSTRAINT fk_game_players_game FOREIGN KEY (game) REFERENCES games(id);
`);
await conn.query(`
ALTER TABLE turns
ADD CONSTRAINT fk_turns_game FOREIGN KEY (game) REFERENCES games(id);
`);
}
} }
]; ];