From e4e53e06f67b6cd518a235769c0d039f2568a310 Mon Sep 17 00:00:00 2001 From: Tzvetan Trave Date: Wed, 15 Oct 2025 18:19:51 +0200 Subject: [PATCH] added tetris game management --- doc/user/user.md | 49 +++++++++++++++++++--------- src/api/user/dMatchHistory.js | 15 +++++++-- src/api/user/dUser.js | 3 +- src/api/user/default.js | 58 +++++++++++++++++++++------------- src/api/user/gMatchHistory.js | 7 ++-- src/api/user/gNumberMatches.js | 6 +++- src/api/user/gUser.js | 2 +- src/api/user/gUsers.js | 14 +++++++- src/api/user/pMatchHistory.js | 30 +++++++++++++----- 9 files changed, 130 insertions(+), 54 deletions(-) diff --git a/doc/user/user.md b/doc/user/user.md index 7821997..da1a991 100644 --- a/doc/user/user.md +++ b/doc/user/user.md @@ -87,13 +87,14 @@ Can return: } ``` -## POST `/users/:userId/matchHistory` +## POST `/users/:userId/matchHistory?game=` -Used to add a match result to an user +Used to add a match result to an user to a specific game Input needed : ```json { + "game": "" "opponent": "", "myScore": , "opponentScore": @@ -107,7 +108,7 @@ Can return: "msg": "Match successfully saved to the blockchain" } ``` -- 400 with response (if no user is specified in header, or no opponent/p1Score/p2Score is specified in body, or opponent is the user specified in header, or a score is negative) +- 400 with response (if no user is specified in header, or no opponent/p1Score/p2Score is specified in body, or opponent is the user specified in header, or a score is negative, or the game specified is invalid) ```json { "error": "" @@ -139,8 +140,14 @@ Can return: { "username": "", "displayName": "", - "wins": , - "losses": + "pong": { + "wins": , + "losses": + }, + "tetris": { + "wins": , + "losses": + } }, ... ] @@ -181,8 +188,14 @@ Can return: { "username": "", "displayName": "", - "wins": , - "losses": + "pong": { + "wins": , + "losses": + }, + "tetris": { + "wins": , + "losses": + } } ``` - 404 with response (if user does not exist) @@ -240,9 +253,9 @@ Can return: } ``` -## GET `/users/:userId/matchHistory?iStart=&iEnd=` +## GET `/users/:userId/matchHistory?game=&iStart=&iEnd=` -Used to get the match history of an user +Used to get the match history of an user for a specific game Can return: - 200 with response (list of matches results (between iStart and iEnd)) @@ -264,7 +277,7 @@ Can return: ] } ``` -- 400 with response (if iStart/iEnd does not exist, or iEnd < iStart) +- 400 with response (if iStart/iEnd does not exist, or iEnd < iStart, or the game specified is invalid) ```json { "error": "" @@ -277,9 +290,9 @@ Can return: } ``` -## GET `/users/:userId/matchHistory/count` +## GET `/users/:userId/matchHistory/count?game=` -Used to get the number of matches an user played +Used to get the number of matches an user played for a specific game Can return: - 200 with response @@ -288,6 +301,12 @@ Can return: "n_": } ``` +- 400 with response (if game does not exist) +```json +{ + "error": "" +} +``` - 404 with response (if user does not exist) ```json { @@ -440,9 +459,9 @@ Can return: } ``` -## DELETE `/users/:userId/matchHistory` +## DELETE `/users/:userId/matchHistory?game=` -Used to delete the match history of an user +Used to delete the match history of an user for a specific game Can return: - 200 with response @@ -451,7 +470,7 @@ Can return: "msg": "Match history deleted successfully" } ``` -- 400 with response (if user specified in header is neither admin nor user) +- 400 with response (if user specified in header is neither admin nor user, or the game specified is invalid) ```json { "error": "" diff --git a/src/api/user/dMatchHistory.js b/src/api/user/dMatchHistory.js index 09efa08..8b7cc0e 100644 --- a/src/api/user/dMatchHistory.js +++ b/src/api/user/dMatchHistory.js @@ -1,4 +1,4 @@ -export async function dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStats) { +export async function dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris) { try { if (!request.user) { return reply.code(400).send({ error: "Please specify a user" }); @@ -10,8 +10,17 @@ export async function dMatchHistory(request, reply, fastify, getUserInfo, delete if (request.user !== 'admin' && request.user !== userId) { return reply.code(401).send({ error: "Unauthorized" }); } - deleteMatchHistory.run(userId); - deleteStats.run(userId); + const { game } = request.query; + if (game !== 'pong' && game !== 'tetris') { + return reply.code(400).send({ error: "Specified game does not exist" }); + } + deleteMatchHistory.run(game, userId); + if (game === 'pong') { + deleteStatsPong.run(userId); + } + else if (game === 'tetris') { + deleteStatsTetris.run(userId); + } return reply.code(200).send({ msg: "Match history deleted successfully" }); } catch (err) { fastify.log.error(err); diff --git a/src/api/user/dUser.js b/src/api/user/dUser.js index 0b0ca0a..d2728af 100644 --- a/src/api/user/dUser.js +++ b/src/api/user/dUser.js @@ -3,7 +3,8 @@ export async function dUser(request, reply, fastify, getUserInfo, deleteMatchHis if (!getUserInfo.get(request.params.userId)) { return reply.code(404).send({ error: "User does not exist" }); } - deleteMatchHistory.run(request.params.userId); + deleteMatchHistory.run('pong', request.params.userId); + deleteMatchHistory.run('tetris', request.params.userId); deleteFriends.run(request.params.userId); deleteUser.run(request.params.userId); return reply.code(200).send({ msg: "User deleted successfully" }); diff --git a/src/api/user/default.js b/src/api/user/default.js index d122bf9..22ca384 100644 --- a/src/api/user/default.js +++ b/src/api/user/default.js @@ -35,11 +35,15 @@ function prepareDB() { id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, displayName TEXT, - wins INTEGER, - losses INTEGER, + pongWins INTEGER, + pongLosses INTEGER, + tetrisWins INTEGER, + tetrisLosses INTEGER, UNIQUE(username), - CHECK(wins >= 0), - CHECK(losses >= 0) + CHECK(pongWins >= 0), + CHECK(pongLosses >= 0), + CHECK(tetrisWins >= 0), + CHECK(tetrisLosses >= 0) ) STRICT `); database.exec(` @@ -54,8 +58,12 @@ function prepareDB() { database.exec(` CREATE TABLE IF NOT EXISTS matchHistory ( id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT, - matchId INTEGER + game TEXT, + player1 TEXT, + player2 TEXT, + matchId INTEGER, + CHECK(player1 != player2), + CHECK(game = 'pong' OR game = 'tetris') ) STRICT `); } @@ -63,34 +71,40 @@ function prepareDB() { prepareDB(); // POST -const createUser = database.prepare('INSERT INTO userData (username, displayName, wins, losses) VALUES (?, ?, 0, 0);'); +const createUser = database.prepare('INSERT INTO userData (username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses) VALUES (?, ?, 0, 0, 0, 0);'); const addFriend = database.prepare('INSERT INTO friends (username, friendName) VALUES (?, ?);'); -const addMatch = database.prepare('INSERT INTO matchHistory (username, matchId) VALUES (?, ?);'); -const incWins = database.prepare('UPDATE userData SET wins = wins + 1 WHERE username = ?;'); -const incLosses = database.prepare('UPDATE userData SET losses = losses + 1 WHERE username = ?'); +const addMatch = database.prepare('INSERT INTO matchHistory (game, player1, player2, matchId) VALUES (?, ?, ?, ?);'); +const incWinsPong = database.prepare('UPDATE userData SET pongWins = pongWins + 1 WHERE username = ?;'); +const incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLosses + 1 WHERE username = ?'); +const incWinsTetris = database.prepare('UPDATE userData SET tetrisWins = tetrisWins + 1 WHERE username = ?;'); +const incLossesTetris = database.prepare('UPDATE userData SET tetrisLosses = tetrisLosses + 1 WHERE username = ?'); // PATCH const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;'); // GET -const getUserData = database.prepare('SELECT username, displayName, wins, losses FROM userData LIMIT ? OFFSET ?;'); -const getUserInfo = database.prepare('SELECT username, displayName, wins, losses FROM userData WHERE username = ?;'); +const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;'); +const getUserInfo = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData WHERE username = ?;'); const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ? LIMIT ? OFFSET ?;'); const getFriend = database.prepare('SELECT friendName FROM friends WHERE username = ? AND friendName = ?;'); -const getMatchHistory = database.prepare('SELECT matchId FROM matchHistory WHERE username = ? LIMIT ? OFFSET ?;'); +const getMatchHistory = database.prepare('SELECT matchId FROM matchHistory WHERE game = ? AND ? IN (player1, player2) LIMIT ? OFFSET ?;'); const getNumberUsers = database.prepare('SELECT COUNT (DISTINCT username) AS n_users FROM userData;'); const getNumberFriends = database.prepare('SELECT COUNT (DISTINCT friendName) AS n_friends FROM friends WHERE username = ?;'); -const getNumberMatches = database.prepare('SELECT COUNT (DISTINCT id) AS n_matches FROM matchHistory WHERE username = ?;') +const getNumberMatches = database.prepare('SELECT COUNT (DISTINCT id) AS n_matches FROM matchHistory WHERE game = ? AND ? IN (player1, player2);') // DELETE const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;'); const deleteFriend = database.prepare('DELETE FROM friends WHERE username = ? AND friendName = ?;'); const deleteFriends = database.prepare('DELETE FROM friends WHERE username = ?;'); -const deleteMatchHistory = database.prepare('DELETE FROM matchHistory WHERE username = ?;'); -const deleteStats = database.prepare('UPDATE userData SET wins = 0, losses = 0 WHERE username = ?;'); +const deleteMatchHistory = database.prepare('DELETE FROM matchHistory WHERE game = ? AND ? IN (player1, player2);'); +const deleteStatsPong = database.prepare('UPDATE userData SET pongWins = 0, pongLosses = 0 WHERE username = ?;'); +const deleteStatsTetris = database.prepare('UPDATE userData SET tetrisWins = 0, tetrisLosses = 0 WHERE username = ?;'); const querySchema = { type: 'object', required: ['iStart', 'iEnd'], properties: { iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } } const bodySchema = { type: 'object', required: ['opponent', 'myScore', 'opponentScore'], properties: { opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } } +const querySchemaMatchHistory = { type: 'object', required: ['game', 'iStart', 'iEnd'], properties: { game: { type: 'string' }, iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } } +const bodySchemaMatchHistory = { type: 'object', required: ['game', 'opponent', 'myScore', 'opponentScore'], properties: { game: { type: 'string' }, opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } } +const querySchemaMatchHistoryGame = { type: 'object', required: ['game'], properties: { game: { type: 'string' } } } export default async function(fastify, options) { fastify.register(fastifyJWT, { @@ -137,10 +151,10 @@ export default async function(fastify, options) { fastify.get('/users/:userId/friends/count', { preHandler: [fastify.authenticate] }, async (request, reply) => { return gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends); }); - fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => { + fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { querystring: querySchemaMatchHistory } }, async (request, reply) => { return gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory); }); - fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate] }, async (request, reply) => { + fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => { return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches); }); @@ -151,8 +165,8 @@ export default async function(fastify, options) { fastify.post('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => { return pFriend(request, reply, fastify, getUserInfo, getFriend, addFriend); }); - fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { body: bodySchema } }, async (request, reply) => { - return pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWins, incLosses); + fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMatchHistory } }, async (request, reply) => { + return pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris); }); // PATCH @@ -173,7 +187,7 @@ export default async function(fastify, options) { fastify.delete('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => { return dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend); }); - fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate] }, async (request, reply) => { - return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStats); + fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => { + return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris); }); } diff --git a/src/api/user/gMatchHistory.js b/src/api/user/gMatchHistory.js index 2566fe3..6fde63e 100644 --- a/src/api/user/gMatchHistory.js +++ b/src/api/user/gMatchHistory.js @@ -4,11 +4,14 @@ export async function gMatchHistory(request, reply, fastify, getUserInfo, getMat if (!getUserInfo.get(userId)) { return reply.code(404).send({ error: "User does not exist" }); } - const { iStart, iEnd } = request.query; + const { game, iStart, iEnd } = request.query; + if (game !== 'pong' && game !== 'tetris') { + return reply.code(400).send({ error: "Specified game does not exist" }); + } if (Number(iEnd) < Number(iStart)) { return reply.code(400).send({ error: "Starting index cannot be strictly inferior to ending index" }); } - const matchHistoryId = getMatchHistory.all(userId, Number(iEnd) - Number(iStart), Number(iStart)); + const matchHistoryId = getMatchHistory.all(game, userId, Number(iEnd) - Number(iStart), Number(iStart)); if (!matchHistoryId.length) { return reply.code(404).send({ error: "No matches exist in the selected range" }); } diff --git a/src/api/user/gNumberMatches.js b/src/api/user/gNumberMatches.js index 7abe3a6..f26e628 100644 --- a/src/api/user/gNumberMatches.js +++ b/src/api/user/gNumberMatches.js @@ -4,7 +4,11 @@ export async function gNumberMatches(request, reply, fastify, getUserInfo, getNu if (!getUserInfo.get(userId)) { return reply.code(404).send({ error: "User does not exist" }); } - const row = getNumberMatches.get(userId); + const { game } = request.query; + if (game !== 'pong' && game !== 'tetris') { + return reply.code(400).send({ error: "Specified game does not exist" }); + } + const row = getNumberMatches.get(game, userId); return reply.code(200).send({ n_matches: row.n_matches }); } catch (err) { fastify.log.error(err); diff --git a/src/api/user/gUser.js b/src/api/user/gUser.js index 4da2426..6937e6c 100644 --- a/src/api/user/gUser.js +++ b/src/api/user/gUser.js @@ -5,7 +5,7 @@ export async function gUser(request, reply, fastify, getUserInfo) { if (!userInfo) { return reply.code(404).send({ error: "User does not exist" }); } - return reply.code(200).send({ username: userInfo.username, displayName: userInfo.displayName, wins: userInfo.wins, losses: userInfo.losses }); + return reply.code(200).send({ username: userInfo.username, displayName: userInfo.displayName, pong: { wins: userInfo.pongWins, losses: userInfo.pongLosses }, tetris: { wins: userInfo.tetrisWins, losses: userInfo.tetrisLosses } }); } catch (err) { fastify.log.error(err); return reply.code(500).send({ error: "Internal server error" }); diff --git a/src/api/user/gUsers.js b/src/api/user/gUsers.js index 4a60d6d..bf42f33 100644 --- a/src/api/user/gUsers.js +++ b/src/api/user/gUsers.js @@ -8,7 +8,19 @@ export async function gUsers(request, reply, fastify, getUserData) { if (!users.length) { return reply.code(404).send({ error: "No users exist in the selected range" }); } - return reply.code(200).send({ users }); + const usersFormat = users.map(obj => ({ + username: obj.username, + displayName: obj.displayName, + pong: { + wins: obj.pongWins, + losses: obj.pongLosses + }, + tetris: { + wins: obj.tetrisWins, + losses: obj.tetrisLosses + } + })); + return reply.code(200).send({ usersFormat }); } catch (err) { fastify.log.error(err); return reply.code(500).send({ error: "Internal server error" }); diff --git a/src/api/user/pMatchHistory.js b/src/api/user/pMatchHistory.js index 1cf17b4..fc346d1 100644 --- a/src/api/user/pMatchHistory.js +++ b/src/api/user/pMatchHistory.js @@ -4,10 +4,10 @@ async function fetchSave(request, reply, userId, addMatch) { throw new Error('Internal server error'); } const data = await res.json(); - addMatch.run(userId, data.id); + addMatch.run(request.body.game, userId, request.body.opponent, data.id); } -export async function pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWins, incLosses) { +export async function pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris) { try { const userId = request.params.userId; if (!request.user) { @@ -16,6 +16,9 @@ export async function pMatchHistory(request, reply, fastify, getUserInfo, addMat if (request.user !== 'admin' && request.user !== userId) { return reply.code(401).send({ error: "Unauthorized" }); } + if (request.body.game !== 'pong' && request.body.game !== 'tetris') { + return reply.code(400).send({ error: "Specified game does not exist" }); + } if (!getUserInfo.get(userId)) { return reply.code(404).send({ error: "User does not exist" }); } @@ -26,12 +29,23 @@ export async function pMatchHistory(request, reply, fastify, getUserInfo, addMat return reply.code(400).send({ error: "Do you have dementia ? You cannot have played a match against yourself gramps" }); } await fetchSave(request, reply, userId, addMatch); - if (request.body.myScore > request.body.opponentScore) { - incWins.run(userId); - incLosses.run(request.body.opponent); - } else if (request.body.myScore < request.body.opponentScore) { - incWins.run(request.body.opponent); - incLosses.run(userId); + if (request.body.game === 'pong') { + if (request.body.myScore > request.body.opponentScore) { + incWinsPong.run(userId); + incLossesPong.run(request.body.opponent); + } else if (request.body.myScore < request.body.opponentScore) { + incWinsPong.run(request.body.opponent); + incLossesPong.run(userId); + } + } + else if (request.body.game === 'tetris') { + if (request.body.myScore > request.body.opponentScore) { + incWinsTetris.run(userId); + incLossesTetris.run(request.body.opponent); + } else if (request.body.myScore < request.body.opponentScore) { + incWinsTetris.run(request.body.opponent); + incLossesTetris.run(userId); + } } return reply.code(200).send({ msg: "Match successfully saved to the blockchain" }); } catch (err) {