From 62aaa32b9da81049ca563507261b59ad8ee80dd7 Mon Sep 17 00:00:00 2001 From: adjoly Date: Sun, 19 Oct 2025 16:42:00 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E3=80=8C=E2=9C=A8=E3=80=8D=20feat:=20added?= =?UTF-8?q?=20ping=20routes=20for=20activity=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user/default.js | 20 ++++++++++++++++++++ src/api/user/gPing.js | 21 +++++++++++++++++++++ src/api/user/pPing.js | 21 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/api/user/gPing.js create mode 100644 src/api/user/pPing.js diff --git a/src/api/user/default.js b/src/api/user/default.js index 1b57c07..cef0c30 100644 --- a/src/api/user/default.js +++ b/src/api/user/default.js @@ -21,6 +21,8 @@ import { dMatchHistory } from './dMatchHistory.js'; import { pAvatar } from './pAvatar.js'; import { gAvatar } from './gAvatar.js'; import { dAvatar } from './dAvatar.js'; +import { pPing } from './pPing.js'; +import { gPing } from './gPing.js'; const env = process.env.NODE_ENV || 'development'; @@ -72,6 +74,12 @@ function prepareDB() { CHECK(date >= 0) ) STRICT `); + database.exec(` + CREATE TABLE IF NOT EXISTS activityTime ( + username TEXT PRIMARY KEY, + time TEXT + ) STRICT + `); } prepareDB(); @@ -85,6 +93,11 @@ const incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLos const incWinsTetris = database.prepare('UPDATE userData SET tetrisWins = tetrisWins + 1 WHERE username = ?;'); const incLossesTetris = database.prepare('UPDATE userData SET tetrisLosses = tetrisLosses + 1 WHERE username = ?'); const setAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;'); +const setActivityTime = database.prepare(` + INSERT INTO activityTime (username, time) + VALUES (?, ?) + ON CONFLICT(username) DO UPDATE SET time = excluded.time; +`); // PATCH const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;'); @@ -100,6 +113,7 @@ const getNumberUsers = database.prepare('SELECT COUNT (DISTINCT username) AS n_u 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 game = ? AND ? IN (player1, player2);'); const getAvatarId = database.prepare('SELECT avatarId FROM userData WHERE username = ?;'); +const getActivityTime = database.prepare('SELECT time FROM activityTime WHERE username = ?;') // DELETE const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;'); @@ -170,6 +184,9 @@ export default async function(fastify, options) { fastify.get('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => { return gAvatar(request, reply, fastify, getAvatarId); }); + fastify.get('/ping/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => { + return gPing(request, reply, fastify, getActivityTime); + }); // POST fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => { @@ -184,6 +201,9 @@ export default async function(fastify, options) { fastify.post('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => { return pAvatar(request, reply, fastify, setAvatarId); }); + fastify.post('/ping', { preHandler: [fastify.authenticate] }, async (request, reply) => { + return pPing(request, reply, fastify, setActivityTime); + }) // PATCH fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => { diff --git a/src/api/user/gPing.js b/src/api/user/gPing.js new file mode 100644 index 0000000..2d2d018 --- /dev/null +++ b/src/api/user/gPing.js @@ -0,0 +1,21 @@ +/** + * @param {import('fastify').FastifyRequest} request + * @param {import('fastify').FastifyReply} reply + * @param {import('fastify').FastifyInstance} fastify + */ +export async function gPing(request, reply, fastify, getActivityTime) { + try { + const user = request.params.userId; + + const time = getActivityTime.get(user); + console.log(time) + + return reply.code(200) + .send({ + lastSeenTime: time.time + }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } +} diff --git a/src/api/user/pPing.js b/src/api/user/pPing.js new file mode 100644 index 0000000..b7ff9fd --- /dev/null +++ b/src/api/user/pPing.js @@ -0,0 +1,21 @@ +/** + * @param {import('fastify').FastifyRequest} request + * @param {import('fastify').FastifyReply} request + * @param {import('fastify').Fastify} fastify + */ +export async function pPing(request, reply, fastify, setActivityTime) { + try { + const user = request.user; + const currentTime = new Date().toISOString(); + + setActivityTime.run(user, currentTime); + + return reply.code(200) + .send({ + msg: "last seen time updated successfully" + }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } +} From 2757c5796d1b9cb6111d0ce95fe021e5f4a203cc Mon Sep 17 00:00:00 2001 From: adjoly Date: Sun, 19 Oct 2025 16:47:24 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=E3=80=8C=F0=9F=93=9D=E3=80=8D=20doc:=20add?= =?UTF-8?q?ed=20doc=20to=20this?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/user/ping.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 doc/user/ping.md diff --git a/doc/user/ping.md b/doc/user/ping.md new file mode 100644 index 0000000..8051bf1 --- /dev/null +++ b/doc/user/ping.md @@ -0,0 +1,41 @@ +# ping + +Available endpoints: +- POST `/ping` +- GET `/ping/:userId` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## POST `/ping` + +Used to send a ping and update the lastSeenTime (can be used for activity time) + +Input needed : just need a valid token + +Can return: +- 200 +```json +{ + "msg": "last seen time updated successfully" +} +``` + +## GET `/ping/:userId` + +Used to retrive the lastSeenTime of a user + +Input needed : just need a valid token + +Can return: +- 200 +```json +{ + "lastSeenTime": "" +} +``` From c4403de09966313e1c72e2d0d8a68c62a51092e2 Mon Sep 17 00:00:00 2001 From: adjoly Date: Sun, 19 Oct 2025 17:02:27 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix:=20cha?= =?UTF-8?q?nge=20to=20isLogged=20boolean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/user/ping.md | 2 +- src/api/user/gPing.js | 37 ++++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/doc/user/ping.md b/doc/user/ping.md index 8051bf1..1e6dded 100644 --- a/doc/user/ping.md +++ b/doc/user/ping.md @@ -36,6 +36,6 @@ Can return: - 200 ```json { - "lastSeenTime": "" + "isLogged": "" } ``` diff --git a/src/api/user/gPing.js b/src/api/user/gPing.js index 2d2d018..b14f003 100644 --- a/src/api/user/gPing.js +++ b/src/api/user/gPing.js @@ -1,21 +1,28 @@ /** - * @param {import('fastify').FastifyRequest} request - * @param {import('fastify').FastifyReply} reply - * @param {import('fastify').FastifyInstance} fastify + * @param {import('fastify').FastifyRequest} request + * @param {import('fastify').FastifyReply} reply + * @param {import('fastify').FastifyInstance} fastify */ export async function gPing(request, reply, fastify, getActivityTime) { - try { - const user = request.params.userId; + try { + const user = request.params.userId; + const time = getActivityTime.get(user); - const time = getActivityTime.get(user); - console.log(time) + if (!time || !time.time) { + return reply.code(404).send({ error: "User not found or no activity time recorded" }); + } - return reply.code(200) - .send({ - lastSeenTime: time.time - }); - } catch (err) { - fastify.log.error(err); - return reply.code(500).send({ error: "Internal server error" }); - } + const lastSeenTime = new Date(time.time); + const now = new Date(); + const oneMinuteAgo = new Date(now.getTime() - 60000); // 60,000 ms = 1 minute + + const isActiveInLastMinute = lastSeenTime >= oneMinuteAgo; + + return reply.code(200).send({ + isLogged: isActiveInLastMinute + }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } }