From 4c4b9ca5efd396f5902dbca425f7bc707721cd5a Mon Sep 17 00:00:00 2001 From: adjoly Date: Mon, 1 Sep 2025 19:00:47 +0200 Subject: [PATCH 001/109] =?UTF-8?q?=E3=80=8C=F0=9F=8E=89=E3=80=8D=20init:?= =?UTF-8?q?=20started=20writing=20doc=20for=20auth=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/auth/2fa.md | 0 doc/auth/login.md | 6 +++++ doc/auth/register.md | 52 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 doc/auth/2fa.md create mode 100644 doc/auth/login.md create mode 100644 doc/auth/register.md diff --git a/doc/auth/2fa.md b/doc/auth/2fa.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/auth/login.md b/doc/auth/login.md new file mode 100644 index 0000000..53f65cc --- /dev/null +++ b/doc/auth/login.md @@ -0,0 +1,6 @@ +# Login + +Abailable endpoints: +- `/login` +- `/login/google` +- `/login/google/callback` diff --git a/doc/auth/register.md b/doc/auth/register.md new file mode 100644 index 0000000..eaabc43 --- /dev/null +++ b/doc/auth/register.md @@ -0,0 +1,52 @@ +# Register + +Available endpoints: +- `/register` +- `/register/google` +- `/register/google/callback` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## `/register` + +Input needed : +```json +{ + "user": "", + "password": "" +} +``` + +Can return: +- 200 with response and cookie in header +```json +{ + "msg": "Register successfully" +} +``` +- 400 with response +```json +{ + "error": "" +} +``` + +## `/register/google` + +Does not take input + +Always return: +- redirect to the google auth url + +## `/register/google/callback` + +inputs are filled by google + +Can return: +- 400 with response From 98a6b50fd002dc47f6ba88e490c6ebf38bcc22c5 Mon Sep 17 00:00:00 2001 From: adjoly Date: Mon, 1 Sep 2025 19:35:37 +0200 Subject: [PATCH 002/109] =?UTF-8?q?=E3=80=8C=F0=9F=93=9D=E3=80=8D=20doc(au?= =?UTF-8?q?th):=20should=20be=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/auth/2fa.md | 68 ++++++++++++++++++++++++++++++++++++++++++++ doc/auth/login.md | 65 ++++++++++++++++++++++++++++++++++++++++-- doc/auth/me.md | 5 ++++ doc/auth/register.md | 29 +++++++++++++------ 4 files changed, 156 insertions(+), 11 deletions(-) create mode 100644 doc/auth/me.md diff --git a/doc/auth/2fa.md b/doc/auth/2fa.md index e69de29..ef087b4 100644 --- a/doc/auth/2fa.md +++ b/doc/auth/2fa.md @@ -0,0 +1,68 @@ +# 2fa + +Abailable endpoints: +- POST `/2fa` +- POST `/2fa/verify` +- DELETE `/2fa` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## POST `/2fa` + +Used to enable 2fa (need to verify after to confirm) + +Inputs: just need a valid JWT cookie + +Returns: +- 200 +```json +{ + "secret": "" + "otpauthUrl": "" +} +``` + +## POST `/2fa/verify` + +Used to confirm 2fa + +Inputs: a valid JWT in cookie and +```json +{ + "token": "" +} +``` + +Returns: +- 200 +```json +{ + "msg": "2FA verified successfully" +} +``` +- 401 || 400 || 404 +```json +{ + "error": "" +} +``` + +## DELETE `/2fa` + +Used to remove 2fa + +Inputs: a valid JWT in cookie + +Returns: +- 200 +```json +{ + "msg": "TOTP removed" +} +``` diff --git a/doc/auth/login.md b/doc/auth/login.md index 53f65cc..76945d3 100644 --- a/doc/auth/login.md +++ b/doc/auth/login.md @@ -1,6 +1,65 @@ # Login Abailable endpoints: -- `/login` -- `/login/google` -- `/login/google/callback` +- POST `/login` +- GET `/login/google` +- GET `/login/google/callback` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## POST `/login` + +Used to login + +Input needed : +```json +{ + "user": "", + "password": "" +} +``` + +Can return: +- 200 with response and cookie in header +```json +{ + "msg": "Login successfully" +} +``` +- 400 with response +```json +{ + "error": "" +} +``` + +## GET `/login/google` + +Used to redirect the user to the login page for google auth + +Always return: +- redirect to the google auth url + +## GET `/login/google/callback` + +Used to get the callback from google and confirm the login + +Can return: +- 400 with response +```json +{ + "error": "" +} +``` +- 200 with response and cookie in header +```json +{ + "msg": "Login successfully" +} +``` diff --git a/doc/auth/me.md b/doc/auth/me.md new file mode 100644 index 0000000..4c907e2 --- /dev/null +++ b/doc/auth/me.md @@ -0,0 +1,5 @@ +GET `/me` + +Inputs : just need the JWT cookie + +Returns the user of the account diff --git a/doc/auth/register.md b/doc/auth/register.md index eaabc43..55caca5 100644 --- a/doc/auth/register.md +++ b/doc/auth/register.md @@ -1,9 +1,9 @@ # Register Available endpoints: -- `/register` -- `/register/google` -- `/register/google/callback` +- POST `/register` +- GET `/register/google` +- GET `/register/google/callback` Common return: - 500 with response @@ -13,7 +13,9 @@ Common return: } ``` -## `/register` +## POST `/register` + +Used to register Input needed : ```json @@ -37,16 +39,27 @@ Can return: } ``` -## `/register/google` +## GET `/register/google` -Does not take input +Used to redirect to the google auth page Always return: - redirect to the google auth url -## `/register/google/callback` +## GET `/register/google/callback` -inputs are filled by google +Used to get the callback from google and register Can return: - 400 with response +```json +{ + "error": "" +} +``` +- 200 with response and cookie in header +```json +{ + "msg": "Register successfully" +} +``` From ba4872fe5bc68642e449c25c418f2c7f8c514104 Mon Sep 17 00:00:00 2001 From: adjoly Date: Tue, 2 Sep 2025 13:12:03 +0200 Subject: [PATCH 003/109] =?UTF-8?q?=E3=80=8C=F0=9F=93=9D=E3=80=8D=20doc(.e?= =?UTF-8?q?nv):=20full?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.env.example b/.env.example index 34d30e2..8ecbc62 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,12 @@ GOOGLE_CALLBACK_URL=https://localhost:8443/api/v1 GOOGLE_CLIENT_SECRET=susAF GOOGLE_CLIENT_ID=Really +AVAX_PRIVATE_KEY= +AVAX_RPC_URL= +AVAX_CONTRACT_ADDR= + +SMTP_SMARTHOST= +SMTP_FROM= +SMTP_AUTH_USERNAME= +SMTP_AUTH_PASSWORD= +EMAIL_TO= From 0ea80ed6381290072dd1cd48e608a32164aa9174 Mon Sep 17 00:00:00 2001 From: Tzvetan Trave Date: Fri, 5 Sep 2025 23:04:44 +0200 Subject: [PATCH 004/109] WIP API, match history done --- src/api/user/TODO | 39 ++++++++++++ src/api/user/default.js | 138 ++++++++++++++++++++++++++++++++-------- 2 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 src/api/user/TODO diff --git a/src/api/user/TODO b/src/api/user/TODO new file mode 100644 index 0000000..83a6f8c --- /dev/null +++ b/src/api/user/TODO @@ -0,0 +1,39 @@ +Todo : +- crate a whole image upload API that ensures files are not executables, converts to a single type, stores the image and returns a UID to address them +- create users with all the necessary arguments (assign avatar randomly if none is provided) +- add endpoints to return number of friends and matches +- use more schema in endpoints ? instead of using many checks everywhere +- split code into files with functions called in the endpoints +- test everything (using Postman) + + + +POST user : +- uploading the avatar involves annoying file handling functions +- avatar must be chosen randomly if not provided + +GET friends : +- should also work with indexes ideally (like GET matchHistory) + +POST friends : +- rework to make work more similarly to POST matchHistory ? + +PATCH : +- changing the avatar involves annoying file handling functions + +DELETE : +- what can be deleted ? +-> users +-> friends +-> user info ? like display name, avatar, or should they just be changeable ? +-> match history ? does it need to be deletable to comply with RGPD ? + + + +Known issues : +- When game ends we must ensure only one match result is written to the blockchain -> not an issue if we do the server-side as the server can make the single post, but if it is client-side we must take care not to send two (either by creating an API for the game that will have the necessary protections or by adding these protections directly into the user API) +-> Right now POST matchHistory will send the two matches to the blockchain API + +-> Users set to private should not appear in the friends lists of other public users + +-> Right now the client can only get his own friends. Do we not want any other client to be able to see his friends ? diff --git a/src/api/user/default.js b/src/api/user/default.js index a5efe65..d95fa8e 100644 --- a/src/api/user/default.js +++ b/src/api/user/default.js @@ -1,23 +1,25 @@ import fastifyJWT from '@fastify/jwt'; import fastifyCookie from '@fastify/cookie'; import Database from 'better-sqlite3'; +import fs from 'fs'; -var env = process.env.NODE_ENV || 'development'; - -let database; +const env = process.env.NODE_ENV || 'development'; if (!env || env === 'development') { - database = new Database(":memory:", { verbose: console.log }); + const database = new Database(":memory:", { verbose: console.log }); } else { - var dbPath = process.env.DB_PATH || '/db/db.sqlite' - database = new Database(dbPath); + const dbPath = process.env.DB_PATH || '/db/db.sqlite' + const database = new Database(dbPath); } function prepareDB() { database.exec(` CREATE TABLE IF NOT EXISTS userData ( username TEXT PRIMARY KEY, - displayName TEXT + displayName TEXT, + avatar BLOB, + wins INTEGER, + losses INTEGER ) STRICT `); database.exec(` @@ -26,30 +28,39 @@ function prepareDB() { friendName TEXT, UNIQUE(username, friendName), CHECK(username != friendName) - ) + ) STRICT + `); + database.exec(` + CREATE TABLE IF NOT EXISTS matchHistory ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT, + matchId INTEGER + ) STRICT `); } prepareDB(); // POST -const createUser = database.prepare('INSERT INTO userData (username, displayName) VALUES (?, ?);'); +const createUser = database.prepare('INSERT INTO userData (username, displayName, avatar, wins, losses) VALUES (?, ?, ?, 0, 0);'); const addFriend = database.prepare('INSERT INTO friends (username, friendName) VALUES (?, ?);'); +const addMatch = database.prepare('INSERT INTO matchHistory (username, matchId) VALUES (?, ?);'); // PATCH const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;'); +const changeAvatar = database.prepare('UPDATE userData SET avatar = ? WHERE username = ?;'); // GET -const getUserInfo = database.prepare('SELECT * FROM userData WHERE username = ?;'); const getUserData = database.prepare('SELECT * FROM userData;'); +const getUserInfo = database.prepare('SELECT * FROM userData WHERE username = ?;'); const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ?;'); -// const isFriend = database.prepare('SELECT 1 FROM friends WHERE username = ? AND friendName = ?;'); +const getMatchHistory = database.prepare('SELECT matchId FROM matchHistory WHERE username = ? AND id BETWEEN ? AND ? ORDER BY id ASC;'); // 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 = ?;'); /** * @param {import('fastify').FastifyInstance} fastify @@ -88,7 +99,6 @@ export default async function(fastify, options) { fastify.get('/users', { preHandler: [fastify.authenticate] }, async (request, reply) => { try { const users = getUserData.all(); - return reply.code(200).send({ users }); } catch (err) { fastify.log.error(err); @@ -98,7 +108,6 @@ export default async function(fastify, options) { fastify.get('/users/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => { try { const info = getUserInfo.get(request.params.userId); - return reply.code(200).send({ info }); } catch (err) { fastify.log.error(err); @@ -108,14 +117,11 @@ export default async function(fastify, options) { fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => { try { const userId = request.params.userId; - if (!getUserInfo.get(userId)) { return reply.code(404).send({ error: "User does not exist" }); } - if (userId == request.user || request.user == 'admin') { const friends = getFriends.all(userId); - if (!friends) { return reply.code(404).send({ error: "User does not have friends D:" }); } @@ -126,22 +132,64 @@ export default async function(fastify, options) { return reply.code(500).send({ error: "Internal server error" }); } }); + fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate] }, async (request, reply) => { + try { + const userId = request.params.userId; + if (!getUserInfo.get(userId)) { + return reply.code(404).send({ error: "User does not exist" }); + } + if (userId == request.user || request.user == 'admin') { + if (!matchHistory) { + return reply.code(404).send({ error: "User has not participated in any matches yet" }); + } + if (!request.body || !request.body.i_start || !request.body.i_end) { + return reply.code(400).send({ error: "Please specify both a strting and an ending index" }); + } + if (request.body.i_end < request.body.i_start) { + return reply.code(400).send({ error: "Starting index cannot be strictly inferior to ending index" }); + } + const matchHistoryId = getMatchHistory.all(userId, request.body.i_start, request.body.i_end - 1); + const promises = matchHistoryId.map(async (id) => { + const res = await fetch('/' + userId, { method: "GET", headers: { "Content-Type": "application/json" } }); + if (!res.ok) + throw new Error('Failed to fetch item ${id}'); + return res.json(); + }); + const matchHistory = await Promise.all(promises); + return reply.code(200).send({ matchHistory }); + } + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } + ); // POST fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => { try { const userId = request.params.userId; - + if (request.user != 'admin') { + return reply.code(401).send({ error: "Unauthorized" }); + } if (getUserInfo.get(userId)) { return reply.code(400).send({ error: "User already exist" }); } - createUser.run(userId, userId); + if (!request.body || !request.body.displayName) { + return reply.code(400).send({ error: "Please specify a display name and an avatar" }); + } + const avatar; + if (request.body.avatar) { + avatar = request.body.avatar; + } else { + avatar = 1;// randomly chosen avatar + } + createUser.run(userId, request.body.displayName, avatar); return reply.code(200).send({ msg: "User created sucessfully" }); } catch (err) { fastify.log.error(err); return reply.code(500).send({ error: "Internal server error" }); } - }) + }); fastify.post('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => { try { const userId = request.params.userId; @@ -167,6 +215,37 @@ export default async function(fastify, options) { return reply.code(500).send({ error: "Internal server error" }); } }); + fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate] }, async (request, reply) => { + try { + const userId = request.params.userId; + if (request.user != 'admin' && request.user != userId) { + return reply.code(401).send({ error: "Unauthorized" }); + } + if (!request.body || !request.body.user || !request.body.p1Score || !request.body.p2Score) { + return reply.code(400).send({ error: "Please specify the second player and the score of both players" }); + } + if (!getUserInfo.get(userId)) { + return reply.code(404).send({ error: "User does not exist" }); + } + if (!getUserInfo.get(request.body.user)) { + return reply.code(404).send({ error: "Second player does not exist" }); + } + if (request.body.user === userId) { + return reply.code(400).send({ error: "Do you have dementia ? You cannot have played a match against yourself, gramps" }); + } + if (request.body.p1Score < 0 || request.body.p2Score < 0) { + return reply.code(400).send({ error: "A score cannot be strictly negative" }); + } + const res = await fetch('/', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ p1: userId, p2: request.body.user, p1Score: request.body.p1Score, p2Score: request.body.p2Score }) }); + if (!res.ok) + return reply.code(500).send({ error: "Internal server error" }); + addMatch.run(userId, res.id); + return reply.code(200).send({ msg: "Match history retrieved successfully" }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } + }); // PATCH fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => { @@ -179,21 +258,28 @@ export default async function(fastify, options) { return reply.code(404).send({ error: "User does not exist" }); } const member = request.params.member; - if (member === 'displayName') { if (!request.body || !request.body.displayName) { return reply.code(400).send({ error: "Please specify a displayName" }); } - changeDisplayName.run(request.body.displayName, userId); - return reply.code(200).send({ msg: "displayName modified sucessfully" }); + return reply.code(200).send({ msg: "Display name modified sucessfully" }); + } + if (member === 'avatar') { + if (!request.body || !request.body.avatar) { + return reply.code(400).send({ error: "Please specify an avatar" }); + } + changeAvatar.run(request.body.avatar, userId); + return reply.code(200).send({ msg: "Avatar modified sucessfully" }); + } + return reply.code(400).send({ error: "Avatar does not exist" }) } return reply.code(400).send({ error: "Member does not exist" }) } catch (err) { fastify.log.error(err); return reply.code(500).send({ error: "Internal server error" }); } - }) + }); // DELETE /** @@ -218,9 +304,9 @@ export default async function(fastify, options) { if (user == 'admin' || user == request.params.userId) { if (member == 'displayName') { changeDisplayName.run("", request.params.userId); - return reply.code(200).send({ msg: "displayName cleared sucessfully" }); + return reply.code(200).send({ msg: "Display name cleared sucessfully" }); } - return reply.code(400).send({ msg: "member does not exist" }) + return reply.code(400).send({ msg: "Member does not exist" }) } else { return reply.code(401).send({ error: 'You dont have the right to delete this' }); } From 89e98bef2a85e5c61f5248b9c704c5e19db8fc54 Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 10 Sep 2025 23:10:20 +0200 Subject: [PATCH 005/109] =?UTF-8?q?=E3=80=8C=E2=9C=A8=E3=80=8D=20feat:=20a?= =?UTF-8?q?dded=20container=20for=20scoreStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/api-base/Dockerfile | 2 +- docker/api-base/compose.yml | 21 +++++++++++++++++++-- docker/volumes.yml | 4 ++++ src/start.js | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docker/api-base/Dockerfile b/docker/api-base/Dockerfile index 142d056..e291122 100644 --- a/docker/api-base/Dockerfile +++ b/docker/api-base/Dockerfile @@ -6,7 +6,7 @@ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml /app/ # install all the dependency RUN npm install -g pnpm RUN cd /app \ - && pnpm install --prod + && pnpm install --prod --frozen-lockfile FROM node:lts-alpine AS base diff --git a/docker/api-base/compose.yml b/docker/api-base/compose.yml index 9ce5386..76b987e 100644 --- a/docker/api-base/compose.yml +++ b/docker/api-base/compose.yml @@ -10,7 +10,6 @@ services: networks: - front - back - - prom-exporter environment: - TZ=Europe/Paris - API_TARGET=user @@ -28,7 +27,6 @@ services: networks: - front - back - - prom-exporter environment: - TZ=Europe/Paris - GOOGLE_CALLBACK_URL=${GOOGLE_CALLBACK_URL} @@ -38,3 +36,22 @@ services: - LOG_FILE_PATH=/var/log/log.log - JWT_SECRET=${JWT_SECRET} restart: unless-stopped + scorestore-api: + container_name: transcendence-api-scoreStore + build: + dockerfile: docker/api-base/Dockerfile + context: ../../ + volumes: + - db-scoreStore:/db + - log-scoreStore:/var/log + networks: + - front + - back + environment: + - TZ=Europe/Paris + - API_TARGET=scoreStore + - LOG_FILE_PATH=/var/log/log.log + - AVAX_PRIVATE_KEY=${AVAX_PRIVATE_KEY} + - AVAX_RPC_URL=${AVAX_RPC_URL} + - AVAX_CONTRACT_ADDR=${AVAX_CONTRACT_ADDR} + restart: unless-stopped diff --git a/docker/volumes.yml b/docker/volumes.yml index f05e822..188877f 100644 --- a/docker/volumes.yml +++ b/docker/volumes.yml @@ -5,9 +5,13 @@ volumes: name: transcendence-api-auth-db db-user: name: transcendence-api-user-db + db-scoreStore: + name: transcendence-api-scoreStore log-auth: name: transcendence-api-auth-log log-user: name: transcendence-api-user-log log-nginx: name: transcendence-front-log + log-scoreStore: + name: transcendence-scoreStore-log diff --git a/src/start.js b/src/start.js index 42f2cbc..6175791 100644 --- a/src/start.js +++ b/src/start.js @@ -58,7 +58,7 @@ async function start() { servers.push(user); } - if (target === 'scoreScore' || target === 'all') { + if (target === 'scoreStore' || target === 'all') { const score = Fastify({ logger: loggerOption('scoreStore') }); score.register(scoreApi); const port = target === 'all' ? 3002 : 3000; From a8713a02d114e368019efd2910de9265fae5fe02 Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 10 Sep 2025 23:28:11 +0200 Subject: [PATCH 006/109] =?UTF-8?q?=E3=80=8C=F0=9F=93=9D=E3=80=8D=20doc(sc?= =?UTF-8?q?oreStore):=20added=20documentation=20on=20the=20behavior=20(tha?= =?UTF-8?q?t=20are=20pretty=20sus)=20of=20the=20scoreStore=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/scoreStore/README.md | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 doc/scoreStore/README.md diff --git a/doc/scoreStore/README.md b/doc/scoreStore/README.md new file mode 100644 index 0000000..2208f89 --- /dev/null +++ b/doc/scoreStore/README.md @@ -0,0 +1,56 @@ +# scoreStore + +Available endpoints: +- GET '/:id' +- POST '/' + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## GET `/:id` + +Used to get an score from the blockchain (the id is the one returned when a score is added) + +Inputs: +:id : the id of the score + +Returns: +- 200 +```json +{ + "score": { + "p1": "", + "p2": "", + "p1Score": "", + "p2Score": "" + }, + "tx": "" +} +``` + +## POST `/` + +Used to add a new score (note that those can't be removed after added) + +Inputs (this one need to be the same as the following otherwise you will have an error 500): +```json +{ + "p1": "", + "p2": "", + "p1Score": "", + "p2Score": "" +} +``` + +Returns: +- 200 +```json +{ + "id": "" +} +``` From cdad9d8a8e53d6742cea483c69d62b6ccd4b68df Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 10 Sep 2025 23:29:11 +0200 Subject: [PATCH 007/109] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix:?= =?UTF-8?q?=20fixed=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/scoreStore/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/scoreStore/README.md b/doc/scoreStore/README.md index 2208f89..0276a44 100644 --- a/doc/scoreStore/README.md +++ b/doc/scoreStore/README.md @@ -1,8 +1,8 @@ # scoreStore Available endpoints: -- GET '/:id' -- POST '/' +- GET `/:id` +- POST `/` Common return: - 500 with response From 9d7ce7f2df4fe1b1c764e9ef39bcf20129d8289b Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 10 Sep 2025 23:31:08 +0200 Subject: [PATCH 008/109] =?UTF-8?q?=E3=80=8C=E2=9C=A8=E3=80=8D=20feat(logs?= =?UTF-8?q?tash/scoreStore):=20added=20scoreStore=20logs=20to=20logstash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/ELK/logstash/compose.yml | 1 + docker/ELK/logstash/pipeline/logstash.conf | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docker/ELK/logstash/compose.yml b/docker/ELK/logstash/compose.yml index be01856..9bc1dd4 100644 --- a/docker/ELK/logstash/compose.yml +++ b/docker/ELK/logstash/compose.yml @@ -8,6 +8,7 @@ services: - log-user:/var/log/user-api - log-auth:/var/log/auth-api - log-nginx:/var/log/nginx + - log-scoreStore:/var/log/scoreStore environment: - LOG_LEVEL=info networks: diff --git a/docker/ELK/logstash/pipeline/logstash.conf b/docker/ELK/logstash/pipeline/logstash.conf index 1a53ae5..aedf06c 100644 --- a/docker/ELK/logstash/pipeline/logstash.conf +++ b/docker/ELK/logstash/pipeline/logstash.conf @@ -19,6 +19,11 @@ input { start_position => "beginning" tags => [ "nginx", "front", "error" ] } + file { + path => "/var/log/scoreStore/log.log" + start_position => "beginning" + tags => [ "api", "scoreStore" ] + } } output { From 4900db1e989c3dfb142668645071269774c7b0ba Mon Sep 17 00:00:00 2001 From: adjoly Date: Sat, 13 Sep 2025 13:15:27 +0200 Subject: [PATCH 009/109] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix(st?= =?UTF-8?q?art):=20fixed=20port=20for=20scoreStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/start.js b/src/start.js index 6175791..df7bc0e 100644 --- a/src/start.js +++ b/src/start.js @@ -61,7 +61,7 @@ async function start() { if (target === 'scoreStore' || target === 'all') { const score = Fastify({ logger: loggerOption('scoreStore') }); score.register(scoreApi); - const port = target === 'all' ? 3002 : 3000; + const port = target === 'all' ? 3003 : 3000; const host = target === 'all' ? '127.0.0.1' : '0.0.0.0'; await score.listen({ port, host }); console.log(`ScoreStore API listening on http://${host}:${port}`); From 22bd4e9dc54133b78f68a68afec785b829ae1ec2 Mon Sep 17 00:00:00 2001 From: y-syo Date: Tue, 2 Sep 2025 02:17:16 +0200 Subject: [PATCH 010/109] =?UTF-8?q?=E3=80=8C=F0=9F=8E=89=E3=80=8D=20init(s?= =?UTF-8?q?rc/front):=20initialized=20the=20frontend,=20ready=20to=20be=20?= =?UTF-8?q?worked=20on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.lock | 6 +++--- flake.nix | 1 + src/front/index.html | 2 ++ src/front/main.tsx | 1 + src/front/style.css | 6 ++++-- 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/front/main.tsx diff --git a/flake.lock b/flake.lock index 782aaad..dade030 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1753250450, - "narHash": "sha256-i+CQV2rPmP8wHxj0aq4siYyohHwVlsh40kV89f3nw1s=", + "lastModified": 1756542300, + "narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "fc02ee70efb805d3b2865908a13ddd4474557ecf", + "rev": "d7600c775f877cd87b4f5a831c28aa94137377aa", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b61cb1a..ecdab7e 100644 --- a/flake.nix +++ b/flake.nix @@ -50,6 +50,7 @@ echo Installing foundry env forge i fi + alias jarvis=just export PATH+=:$(pwd)/node_modules/.bin echo entering ft_trans env ''; diff --git a/src/front/index.html b/src/front/index.html index f2f5a6a..e7a3f05 100644 --- a/src/front/index.html +++ b/src/front/index.html @@ -7,6 +7,8 @@ + +

Vite + Tailwind

🚀 Looks like it's working!

diff --git a/src/front/main.tsx b/src/front/main.tsx new file mode 100644 index 0000000..2a2392b --- /dev/null +++ b/src/front/main.tsx @@ -0,0 +1 @@ +console.log("test") diff --git a/src/front/style.css b/src/front/style.css index b5c61c9..f5645cf 100644 --- a/src/front/style.css +++ b/src/front/style.css @@ -1,3 +1,5 @@ -@tailwind base; +@import "tailwindcss"; + +/*@tailwind base; @tailwind components; -@tailwind utilities; +@tailwind utilities;*/ From f0c764cc0c83658f44aa45f52299eb8826e4245e Mon Sep 17 00:00:00 2001 From: yosyo Date: Sat, 13 Sep 2025 15:57:43 +0200 Subject: [PATCH 011/109] =?UTF-8?q?=E3=80=8C=F0=9F=8F=97=EF=B8=8F=E3=80=8D?= =?UTF-8?q?=20wip(src/front):=20first=20try=20at=20a=20basic=20architectur?= =?UTF-8?q?e.=20game=20logic=20+=20SPA=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.nix | 2 +- src/front/index.html | 41 +++-- src/front/main.tsx | 1 - src/front/static/assets/favicon.ico | Bin 0 -> 16141 bytes src/front/static/css/style.css | 5 + src/front/static/ts/main.ts | 69 +++++++ src/front/static/ts/views/Aview.ts | 10 + src/front/static/ts/views/Game.ts | 214 ++++++++++++++++++++++ src/front/static/ts/views/LoginPage.ts | 66 +++++++ src/front/static/ts/views/MainMenu.ts | 22 +++ src/front/static/ts/views/PongMenu.ts | 21 +++ src/front/static/ts/views/RegisterPage.ts | 27 +++ src/front/style.css | 5 - 13 files changed, 459 insertions(+), 24 deletions(-) delete mode 100644 src/front/main.tsx create mode 100644 src/front/static/assets/favicon.ico create mode 100644 src/front/static/css/style.css create mode 100644 src/front/static/ts/main.ts create mode 100644 src/front/static/ts/views/Aview.ts create mode 100644 src/front/static/ts/views/Game.ts create mode 100644 src/front/static/ts/views/LoginPage.ts create mode 100644 src/front/static/ts/views/MainMenu.ts create mode 100644 src/front/static/ts/views/PongMenu.ts create mode 100644 src/front/static/ts/views/RegisterPage.ts delete mode 100644 src/front/style.css diff --git a/flake.nix b/flake.nix index ecdab7e..fb52ec3 100644 --- a/flake.nix +++ b/flake.nix @@ -39,7 +39,7 @@ nodejs_22 pnpm just - foundry + foundry ]; shellHook = '' if [ ! -d node_modules/ ]; then diff --git a/src/front/index.html b/src/front/index.html index e7a3f05..97c4aea 100644 --- a/src/front/index.html +++ b/src/front/index.html @@ -1,21 +1,28 @@ - - - - Vite + Tailwind Test - - - - - -
-

Vite + Tailwind

-

🚀 Looks like it's working!

- -
- + + + + + + Vite + Tailwind Test + + + + + + + + + + +
+
+ + + diff --git a/src/front/main.tsx b/src/front/main.tsx deleted file mode 100644 index 2a2392b..0000000 --- a/src/front/main.tsx +++ /dev/null @@ -1 +0,0 @@ -console.log("test") diff --git a/src/front/static/assets/favicon.ico b/src/front/static/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..18945be5f045a1017b625d28282b42dc3a4a59c7 GIT binary patch literal 16141 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 literal 0 HcmV?d00001 diff --git a/src/front/static/css/style.css b/src/front/static/css/style.css new file mode 100644 index 0000000..0d686fe --- /dev/null +++ b/src/front/static/css/style.css @@ -0,0 +1,5 @@ +@import "tailwindcss"; + +@theme { + --color-accent-500: #f55151; +} diff --git a/src/front/static/ts/main.ts b/src/front/static/ts/main.ts new file mode 100644 index 0000000..676a313 --- /dev/null +++ b/src/front/static/ts/main.ts @@ -0,0 +1,69 @@ +/*import MainMenu from "./views/MainMenu.ts"; +import PongMenu from "./views/PongMenu.ts"; + +import LoginPage from "./views/LoginPage.ts"; +import RegisterPage from "./views/RegisterPage.ts"; + +import Game from "./views/Game.ts";*/ + +const navigationManager = url => { + history.pushState(null, null, url); + router(); +}; + +let view; + +const routes = [ + /*{ path: "/", view: MainMenu }, + + { path: "/pong", view: PongMenu }, + { path: "/pong/solo", view: Game }, + + { path: "/login", view: LoginPage }, + { path: "/register", view: RegisterPage },*/ + { path: "/", view: () => import("./views/MainMenu.ts") }, + + { path: "/pong", view: () => import("./views/PongMenu.ts") }, + { path: "/pong/solo", view: () => import("./views/Game.ts") }, + + { path: "/login", view: () => import("./views/LoginPage.ts") }, + { path: "/register", view: () => import("./views/RegisterPage.ts") }, +]; + +const router = async () => { + + const routesMap = routes.map(route => { + return { route: route, isMatch: location.pathname === route.path }; + }); + + let match = routesMap.find(routeMap => routeMap.isMatch); + + if (!match) + match = { route: routes[0], isMatch: true }; + + if (view) + view.running = false; + + console.log(match); + + const module = await match.route.view(); + view = new module.default(); + + document.querySelector("#app").innerHTML = await view.getHTML(); + view.run(); +}; + +window.addEventListener("popstate", router); + +document.addEventListener("DOMContentLoaded", () => { + + document.body.addEventListener("click", e=> { + if (e.target.matches("[data-link]")) + { + e.preventDefault(); + navigationManager(e.target.href); + } + }); + + router(); +}); diff --git a/src/front/static/ts/views/Aview.ts b/src/front/static/ts/views/Aview.ts new file mode 100644 index 0000000..810d3ab --- /dev/null +++ b/src/front/static/ts/views/Aview.ts @@ -0,0 +1,10 @@ +export default class { + contructor() + { + } + + setTitle(title) { document.title = title; } + + async getHTML() { return ""; } + async run() { } +}; diff --git a/src/front/static/ts/views/Game.ts b/src/front/static/ts/views/Game.ts new file mode 100644 index 0000000..5610676 --- /dev/null +++ b/src/front/static/ts/views/Game.ts @@ -0,0 +1,214 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + running: boolean; + + constructor() + { + super(); + this.setTitle("pog or pong ? :3"); + this.running = true; + } + + async getHTML() { + return ` + +
+ + +
+ `; + } + + async run() { + let start: number = 0; + let elapsed: number; + + let game_playing: boolean = false; + let match_over: boolean = false; + let p1_score: number = 0; + let p2_score: number = 0; + + let countdown: number = 3; + let countdownTimer: number = 0; + + const canvas = document.getElementById("gameCanvas") as HTMLCanvasElement; + const ctx = canvas.getContext("2d"); + + const paddleOffset: number = 15; + const paddleHeight: number = 100; + const paddleWidth: number = 10; + const ballSize: number = 10; + + let leftPaddleY: number = canvas.height / 2 - paddleHeight / 2; + let rightPaddleY: number = canvas.height / 2 - paddleHeight / 2; + let paddleSpeed: number = 727 * 0.69; + let ballX: number = canvas.width / 2; + let ballY: number = canvas.height / 2; + let ballSpeed: number = 200; + let ballSpeedX: number = 300; + let ballSpeedY: number = 10; + + const keys: Record = {}; + + document.addEventListener("keydown", e => { keys[e.key] = true; }); + document.addEventListener("keyup", e => { keys[e.key] = false; }); + + function movePaddles() { + if (keys["w"] && leftPaddleY > 0) + leftPaddleY -= paddleSpeed * elapsed; + if (keys["s"] && leftPaddleY < canvas.height - paddleHeight) + leftPaddleY += paddleSpeed * elapsed; + if (keys["ArrowUp"] && rightPaddleY > 0) + rightPaddleY -= paddleSpeed * elapsed; + if (keys["ArrowDown"] && rightPaddleY < canvas.height - paddleHeight) + rightPaddleY += paddleSpeed * elapsed; + } + + function getBounceVelocity(paddleY) { + const speed = ballSpeed; + const paddleCenterY = paddleY + paddleHeight / 2; + + let n = (ballY - paddleCenterY) / (paddleHeight / 2); + n = Math.max(-1, Math.min(1, n)); + let theta = n * ((75 * Math.PI) / 180); + ballSpeedY = ballSpeed * Math.sin(theta); + } + + function moveBall() { + let length = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY); + let scale = ballSpeed / length; + ballX += (ballSpeedX * scale) * elapsed; + ballY += (ballSpeedY * scale) * elapsed; + + if (ballY <= 0 || ballY >= canvas.height - ballSize) + ballSpeedY *= -1; + + if (ballX <= paddleWidth + paddleOffset && ballX >= paddleOffset && + ballY > leftPaddleY && ballY < leftPaddleY + paddleHeight) + { + ballSpeedX *= -1; + ballX = paddleWidth + paddleOffset; + getBounceVelocity(leftPaddleY); + ballSpeed += 10; + } + + if (ballX >= canvas.width - paddleWidth - ballSize - paddleOffset && ballX <= canvas.width - ballSize - paddleOffset && + ballY > rightPaddleY && ballY < rightPaddleY + paddleHeight) + { + ballSpeedX *= -1; + ballX = canvas.width - paddleWidth - ballSize - paddleOffset; + getBounceVelocity(rightPaddleY); + ballSpeed += 10; + } + + // scoring + if (ballX < 0 || ballX > canvas.width - ballSize) + { + game_playing = false; + if (ballX < 0) + p2_score++; + else + p1_score++; + + if (p1_score === 3 || p2_score === 3) + match_over = true; + else + { + countdown = 3; + countdownTimer = performance.now(); + } + + ballX = canvas.width / 2; + ballY = canvas.height / 2; + ballSpeed = 200; + ballSpeedX = 300 * ((ballSpeedX > 0) ? 1 : -1); + ballSpeedY = 10; + ballSpeedX = -ballSpeedX; + leftPaddleY = canvas.height / 2 - paddleHeight / 2; + rightPaddleY = canvas.height / 2 - paddleHeight / 2; + } + } + + function draw() { + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = "white"; + ctx.fillRect(paddleOffset, leftPaddleY, paddleWidth, paddleHeight); + ctx.fillRect(canvas.width - paddleWidth - paddleOffset, rightPaddleY, paddleWidth, paddleHeight); + + ctx.fillStyle = "white"; + if (game_playing) + ctx.fillRect(ballX, ballY, ballSize, ballSize); + + ctx.font = "24px sans-serif"; + ctx.fillText(`${p1_score} - ${p2_score}`, canvas.width / 2 - 20, 30); + + if (match_over) + { + ctx.font = "48px sans-serif"; + const winner = p1_score > p2_score ? "Player 1" : "Player 2"; + ctx.fillText(`${winner} won :D`, canvas.width / 2 - 150, canvas.height / 2 + 22); + document.getElementById("game-buttons").classList.remove("hidden"); + } + } + + function startCountdown() + { + const now = performance.now(); + if (countdown > 0) + { + if (now - countdownTimer >= 500) + { + countdown--; + countdownTimer = now; + } + ctx.font = "48px sans-serif"; + ctx.fillText(countdown.toString(), canvas.width / 2 - 10, canvas.height / 2 + 24); + } + else if (countdown === 0) + { + ctx.font = "48px sans-serif"; + ctx.fillText("Go!", canvas.width / 2 - 30, canvas.height / 2 + 24); + setTimeout(() => { + game_playing = true; + countdown = -1; + }, 500); + } + } + + const gameLoop = (timestamp: number) => { + elapsed = (timestamp - start) / 1000; + start = timestamp; + if (game_playing) + { + movePaddles(); + moveBall(); + } + draw(); + console.log(game_playing); + if (!game_playing) + startCountdown(); + if (this.running) + requestAnimationFrame(gameLoop); + }; + + + document.getElementById("game-retry")?.addEventListener("click", () => { + document.getElementById("game-buttons").classList.add("hidden"); + game_playing = false; + match_over = false; + p1_score = 0; + p2_score = 0; + + countdown = 3; + countdownTimer = performance.now(); + }); + requestAnimationFrame(gameLoop); + } +} diff --git a/src/front/static/ts/views/LoginPage.ts b/src/front/static/ts/views/LoginPage.ts new file mode 100644 index 0000000..bdfc553 --- /dev/null +++ b/src/front/static/ts/views/LoginPage.ts @@ -0,0 +1,66 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + constructor() + { + super(); + this.setTitle("login"); + } + + async getHTML() { + return ` +
+

login

+ + + + + + + + register + +
+ `; + } + + async run() { + const login = async () => { + const username = (document.getElementById("username") as HTMLInputElement).value; + const password = (document.getElementById("password") as HTMLInputElement).value; + + try { + /*const response = await fetch("https://localhost/login", { + method: "POST", + headers: { "Content-Type": "application/json", }, + credentials: "include", + body: JSON.stringify({ user: username, password: password }), + }); + + const data = await response.json();*/ + const data = { "error": "invalid password or smth" }; + const response = { status: 400}; + + + if (response.status === 200) + { + navigationManager("/"); + } + else if (response.status === 400) + { + document.getElementById("login-error-message").innerHTML = "error: " + data.error; + document.getElementById("login-error-message").classList.remove("hidden"); + } + + } + catch (error) + { + document.getElementById("login-error-message").innerHTML = "error: server error, try again later..."; + document.getElementById("login-error-message").classList.remove("hidden"); + } + }; + + document.getElementById("login-button")?.addEventListener("click", login); + } +} diff --git a/src/front/static/ts/views/MainMenu.ts b/src/front/static/ts/views/MainMenu.ts new file mode 100644 index 0000000..9d00ee0 --- /dev/null +++ b/src/front/static/ts/views/MainMenu.ts @@ -0,0 +1,22 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + constructor() + { + super(); + this.setTitle("knl is trans(cendence)"); + } + + async getHTML() { + return ` +
+

knl_meowscendence :D

+

i like pong

+ + Pong + +
+ `; + } +} diff --git a/src/front/static/ts/views/PongMenu.ts b/src/front/static/ts/views/PongMenu.ts new file mode 100644 index 0000000..90dabdd --- /dev/null +++ b/src/front/static/ts/views/PongMenu.ts @@ -0,0 +1,21 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + constructor() + { + super(); + this.setTitle("ponging ur mom"); + } + + async getHTML() { + return ` +
+

pong is funny yay

+ + solo + +
+ `; + } +} diff --git a/src/front/static/ts/views/RegisterPage.ts b/src/front/static/ts/views/RegisterPage.ts new file mode 100644 index 0000000..7e97a62 --- /dev/null +++ b/src/front/static/ts/views/RegisterPage.ts @@ -0,0 +1,27 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + constructor() + { + super(); + this.setTitle("ft_trans 🏳️‍⚧️"); + } + + async getHTML() { + return ` +
+

register

+ + + + + + + + i already have an account + +
+ `; + } +} diff --git a/src/front/style.css b/src/front/style.css deleted file mode 100644 index f5645cf..0000000 --- a/src/front/style.css +++ /dev/null @@ -1,5 +0,0 @@ -@import "tailwindcss"; - -/*@tailwind base; -@tailwind components; -@tailwind utilities;*/ From a93d2957890b1ce1b4f0f91828885bbeedcd88cf Mon Sep 17 00:00:00 2001 From: yosyo Date: Wed, 24 Sep 2025 13:00:31 +0200 Subject: [PATCH 012/109] =?UTF-8?q?=E3=80=8C=F0=9F=9A=A7=E3=80=8D=20test(s?= =?UTF-8?q?rc/front):=20wip=20of=20the=20front?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/front/static/css/style.css | 7 +- src/front/static/ts/main.ts | 18 +-- src/front/static/ts/views/Game.ts | 5 +- src/front/static/ts/views/LoginPage.ts | 13 +- src/front/static/ts/views/PongMenu.ts | 15 ++- src/front/static/ts/views/RegisterPage.ts | 6 +- src/front/static/ts/views/TournamentMenu.ts | 141 ++++++++++++++++++++ tailwind.config.js | 2 +- 8 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 src/front/static/ts/views/TournamentMenu.ts diff --git a/src/front/static/css/style.css b/src/front/static/css/style.css index 0d686fe..09f3bdb 100644 --- a/src/front/static/css/style.css +++ b/src/front/static/css/style.css @@ -1,5 +1,8 @@ @import "tailwindcss"; -@theme { +@source inline("space-y-{18,46,102,214,438,886,1782,3574,7158,14326,28662,57334,114678,229366,458742,917494}"); +@source inline("mt-{28,56,84,112}"); + +/*@theme { --color-accent-500: #f55151; -} +}*/ diff --git a/src/front/static/ts/main.ts b/src/front/static/ts/main.ts index 676a313..8ce2cc8 100644 --- a/src/front/static/ts/main.ts +++ b/src/front/static/ts/main.ts @@ -1,11 +1,3 @@ -/*import MainMenu from "./views/MainMenu.ts"; -import PongMenu from "./views/PongMenu.ts"; - -import LoginPage from "./views/LoginPage.ts"; -import RegisterPage from "./views/RegisterPage.ts"; - -import Game from "./views/Game.ts";*/ - const navigationManager = url => { history.pushState(null, null, url); router(); @@ -14,17 +6,11 @@ const navigationManager = url => { let view; const routes = [ - /*{ path: "/", view: MainMenu }, - - { path: "/pong", view: PongMenu }, - { path: "/pong/solo", view: Game }, - - { path: "/login", view: LoginPage }, - { path: "/register", view: RegisterPage },*/ { path: "/", view: () => import("./views/MainMenu.ts") }, { path: "/pong", view: () => import("./views/PongMenu.ts") }, - { path: "/pong/solo", view: () => import("./views/Game.ts") }, + { path: "/pong/local", view: () => import("./views/Game.ts") }, + { path: "/pong/tournament", view: () => import("./views/TournamentMenu.ts") }, { path: "/login", view: () => import("./views/LoginPage.ts") }, { path: "/register", view: () => import("./views/RegisterPage.ts") }, diff --git a/src/front/static/ts/views/Game.ts b/src/front/static/ts/views/Game.ts index 5610676..3bd39b5 100644 --- a/src/front/static/ts/views/Game.ts +++ b/src/front/static/ts/views/Game.ts @@ -7,7 +7,7 @@ export default class extends Aview { constructor() { super(); - this.setTitle("pog or pong ? :3"); + this.setTitle("pong (local match)"); this.running = true; } @@ -67,7 +67,7 @@ export default class extends Aview { rightPaddleY -= paddleSpeed * elapsed; if (keys["ArrowDown"] && rightPaddleY < canvas.height - paddleHeight) rightPaddleY += paddleSpeed * elapsed; - } + } function getBounceVelocity(paddleY) { const speed = ballSpeed; @@ -209,6 +209,7 @@ export default class extends Aview { countdown = 3; countdownTimer = performance.now(); }); + requestAnimationFrame(gameLoop); } } diff --git a/src/front/static/ts/views/LoginPage.ts b/src/front/static/ts/views/LoginPage.ts index bdfc553..aa7a5c0 100644 --- a/src/front/static/ts/views/LoginPage.ts +++ b/src/front/static/ts/views/LoginPage.ts @@ -13,8 +13,8 @@ export default class extends Aview {

login

- - + + @@ -31,16 +31,16 @@ export default class extends Aview { const password = (document.getElementById("password") as HTMLInputElement).value; try { - /*const response = await fetch("https://localhost/login", { + const response = await fetch("http://localhost:3001/login", { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ user: username, password: password }), }); - const data = await response.json();*/ - const data = { "error": "invalid password or smth" }; - const response = { status: 400}; + const data = await response.json(); + /*const data = { "error": "invalid password or smth" }; + const response = { status: 400};*/ if (response.status === 200) @@ -56,6 +56,7 @@ export default class extends Aview { } catch (error) { + console.log(error); document.getElementById("login-error-message").innerHTML = "error: server error, try again later..."; document.getElementById("login-error-message").classList.remove("hidden"); } diff --git a/src/front/static/ts/views/PongMenu.ts b/src/front/static/ts/views/PongMenu.ts index 90dabdd..718b1c0 100644 --- a/src/front/static/ts/views/PongMenu.ts +++ b/src/front/static/ts/views/PongMenu.ts @@ -5,16 +5,21 @@ export default class extends Aview { constructor() { super(); - this.setTitle("ponging ur mom"); + this.setTitle("knl is trans(cendence)"); } async getHTML() { return ` -
+ `; } diff --git a/src/front/static/ts/views/RegisterPage.ts b/src/front/static/ts/views/RegisterPage.ts index 7e97a62..d0534ab 100644 --- a/src/front/static/ts/views/RegisterPage.ts +++ b/src/front/static/ts/views/RegisterPage.ts @@ -5,7 +5,7 @@ export default class extends Aview { constructor() { super(); - this.setTitle("ft_trans 🏳️‍⚧️"); + this.setTitle("register"); } async getHTML() { @@ -13,8 +13,8 @@ export default class extends Aview {

register

- - + + diff --git a/src/front/static/ts/views/TournamentMenu.ts b/src/front/static/ts/views/TournamentMenu.ts new file mode 100644 index 0000000..c36b3d7 --- /dev/null +++ b/src/front/static/ts/views/TournamentMenu.ts @@ -0,0 +1,141 @@ +import Aview from "./Aview.ts" + +export default class extends Aview { + + constructor() + { + super(); + this.setTitle("Tournament"); + } + + async getHTML() { + return ` +
+

how many players ?

+
+ + +
+
+
+ `; + } + + async run() { + const generateBracket = async (playerCount: number) => { + document.getElementById("bracket").innerHTML = ""; + + const rounds = Math.ceil(Math.log2(playerCount)); + const totalSlots = 2 ** rounds; + const byes = totalSlots - playerCount; + + let odd = 0; + if (playerCount % 2) + { + console.error("odd numbers are temporarily invalids"); + return ; + /*++odd; + --playerCount;*/ + } + + let notPowPlayersCount = 0; + + if ((playerCount & (playerCount - 1)) != 0) + notPowPlayersCount = playerCount - (2 ** Math.floor(Math.log2(playerCount))); + + + let initialPlayers = Array.from({ length: 2 ** Math.floor(Math.log2(playerCount))}, (_, i) => `Player ${i + 1}`); + playerCount = 2 ** Math.floor(Math.log2(playerCount)); + //let initialPlayers = Array.from({ length: playerCount }, (_, i) => `Player ${i + 1}`); + + const bracketWrapper = document.createElement("div"); + bracketWrapper.className = "flex space-x-8 overflow-x-auto"; + + // Round 0: Player input column + const playerInputColumn = document.createElement("div"); + playerInputColumn.className = `flex flex-col mt-${(notPowPlayersCount + odd) * 28} space-y-4`; + + initialPlayers.forEach((name, i) => { + const input = document.createElement("input"); + input.type = "text"; + input.id = `playerName${i}`; + input.value = ""; + input.placeholder = name; + input.className = + "w-32 h-10 p-2 text-sm border rounded bg-white shadow disabled:bg-gray-200"; + playerInputColumn.appendChild(input); + }); + + bracketWrapper.appendChild(playerInputColumn); + + let currentRound = initialPlayers; + let previousPadding = 4; + for (let round = 1; round <= rounds; round++) + { + const roundColumn = document.createElement("div"); + previousPadding = previousPadding * 2 + 10 + roundColumn.className = `flex flex-col justify-center space-y-${previousPadding}`; + + const nextRound: string[] = []; + + if (!notPowPlayersCount) + { + if (odd) + { + const input = document.createElement("input"); + input.type = "text"; + input.id = `playerName${playerCount}`; + input.value = ""; + input.placeholder = `Player ${++playerCount}`; + input.className = + "w-32 h-10 p-2 text-sm border rounded bg-white shadow disabled:bg-gray-200"; + roundColumn.appendChild(input); + odd--; + nextRound.push(""); + } + } + + while (notPowPlayersCount) + { + const input = document.createElement("input"); + input.type = "text"; + input.id = `playerName${playerCount}`; + input.value = ""; + input.placeholder = `Player ${++playerCount}`; + input.className = + "w-32 h-10 p-2 text-sm border rounded bg-white shadow disabled:bg-gray-200"; + roundColumn.appendChild(input); + --notPowPlayersCount; + nextRound.push(""); + } + + for (let i = 0; i < currentRound.length; i += 2) + { + const p1 = currentRound[i]; + const p2 = currentRound[i + 1]; + + const matchDiv = document.createElement("div"); + matchDiv.className = + "w-32 h-10 flex items-center justify-center bg-white border rounded shadow text-center text-sm"; + + matchDiv.textContent = ""; + nextRound.push(""); + + roundColumn.appendChild(matchDiv); + } + + bracketWrapper.appendChild(roundColumn); + currentRound = nextRound; + } + + document.getElementById("bracket").appendChild(bracketWrapper); + }; + + document.getElementById("bracket-generate")?.addEventListener("click", () => { + const input: HTMLInputElement = document.getElementById("playerNumber") as HTMLInputElement; + generateBracket(+input.value); + }); + + + } +} diff --git a/tailwind.config.js b/tailwind.config.js index d2fdab4..9bd8368 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,5 @@ export default { - content: ['./src/front/**/*.{html,js}'], + content: ['./src/front/**/*.{html,js,ts,css}'], theme: { extend: {}, }, From 1d2bb02e65df0b193a6a7cb3394fe6842c882646 Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 24 Sep 2025 14:12:05 +0200 Subject: [PATCH 013/109] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix(co?= =?UTF-8?q?rs=20issue):=20should=20be=20working=20as=20expected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 11 +++++++++++ src/api/auth/default.js | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/package.json b/package.json index 7eedbe9..93287eb 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "dependencies": { "@avalabs/avalanchejs": "^5.0.0", "@fastify/cookie": "^11.0.2", + "@fastify/cors": "^11.1.0", "@fastify/env": "^5.0.2", "@fastify/jwt": "^9.1.0", "axios": "^1.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91f44c7..0a5a097 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@fastify/cookie': specifier: ^11.0.2 version: 11.0.2 + '@fastify/cors': + specifier: ^11.1.0 + version: 11.1.0 '@fastify/env': specifier: ^5.0.2 version: 5.0.2 @@ -255,6 +258,9 @@ packages: '@fastify/cookie@11.0.2': resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} + '@fastify/cors@11.1.0': + resolution: {integrity: sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA==} + '@fastify/deepmerge@2.0.2': resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==} @@ -1758,6 +1764,11 @@ snapshots: cookie: 1.0.2 fastify-plugin: 5.0.1 + '@fastify/cors@11.1.0': + dependencies: + fastify-plugin: 5.0.1 + toad-cache: 3.7.0 + '@fastify/deepmerge@2.0.2': {} '@fastify/env@5.0.2': diff --git a/src/api/auth/default.js b/src/api/auth/default.js index 6a15651..dd706e8 100644 --- a/src/api/auth/default.js +++ b/src/api/auth/default.js @@ -1,5 +1,6 @@ import fastifyJWT from '@fastify/jwt'; import fastifyCookie from '@fastify/cookie'; +import cors from '@fastify/cors' import { register } from './register.js'; import { login } from './login.js'; @@ -22,6 +23,11 @@ authDB.prepareDB(); */ export default async function(fastify, options) { + fastify.register(cors, { + origin: "*", + methods: ["GET", "POST", "DELETE"] + }); + fastify.register(fastifyJWT, { secret: process.env.JWT_SECRET || '123456789101112131415161718192021', cookie: { From cb8823fcf3727eafb62e3dbff6f1c6c17734a5fc Mon Sep 17 00:00:00 2001 From: yosyo Date: Sat, 27 Sep 2025 15:50:09 +0200 Subject: [PATCH 014/109] =?UTF-8?q?=E3=80=8C=F0=9F=8F=97=EF=B8=8F=E3=80=8D?= =?UTF-8?q?=20wip:=20tmp=20commit,=20will=20be=20overriden=20later,=20can?= =?UTF-8?q?=20be=20ignored?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 11 +++++++ src/api/auth/default.js | 7 +++++ src/front/static/ts/views/LoginPage.ts | 4 --- src/front/static/ts/views/RegisterPage.ts | 38 ++++++++++++++++++++++- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7eedbe9..93287eb 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "dependencies": { "@avalabs/avalanchejs": "^5.0.0", "@fastify/cookie": "^11.0.2", + "@fastify/cors": "^11.1.0", "@fastify/env": "^5.0.2", "@fastify/jwt": "^9.1.0", "axios": "^1.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91f44c7..0a5a097 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@fastify/cookie': specifier: ^11.0.2 version: 11.0.2 + '@fastify/cors': + specifier: ^11.1.0 + version: 11.1.0 '@fastify/env': specifier: ^5.0.2 version: 5.0.2 @@ -255,6 +258,9 @@ packages: '@fastify/cookie@11.0.2': resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} + '@fastify/cors@11.1.0': + resolution: {integrity: sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA==} + '@fastify/deepmerge@2.0.2': resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==} @@ -1758,6 +1764,11 @@ snapshots: cookie: 1.0.2 fastify-plugin: 5.0.1 + '@fastify/cors@11.1.0': + dependencies: + fastify-plugin: 5.0.1 + toad-cache: 3.7.0 + '@fastify/deepmerge@2.0.2': {} '@fastify/env@5.0.2': diff --git a/src/api/auth/default.js b/src/api/auth/default.js index 6a15651..0650fa1 100644 --- a/src/api/auth/default.js +++ b/src/api/auth/default.js @@ -1,5 +1,6 @@ import fastifyJWT from '@fastify/jwt'; import fastifyCookie from '@fastify/cookie'; +import cors from '@fastify/cors' import { register } from './register.js'; import { login } from './login.js'; @@ -22,6 +23,12 @@ authDB.prepareDB(); */ export default async function(fastify, options) { + fastify.register(cors, { + origin: "http://localhost:5173", + credentials: true, + methods: [ "GET", "POST", "DELETE", "OPTIONS" ] + }); + fastify.register(fastifyJWT, { secret: process.env.JWT_SECRET || '123456789101112131415161718192021', cookie: { diff --git a/src/front/static/ts/views/LoginPage.ts b/src/front/static/ts/views/LoginPage.ts index aa7a5c0..c8dca62 100644 --- a/src/front/static/ts/views/LoginPage.ts +++ b/src/front/static/ts/views/LoginPage.ts @@ -37,11 +37,7 @@ export default class extends Aview { credentials: "include", body: JSON.stringify({ user: username, password: password }), }); - const data = await response.json(); - /*const data = { "error": "invalid password or smth" }; - const response = { status: 400};*/ - if (response.status === 200) { diff --git a/src/front/static/ts/views/RegisterPage.ts b/src/front/static/ts/views/RegisterPage.ts index d0534ab..13fdf55 100644 --- a/src/front/static/ts/views/RegisterPage.ts +++ b/src/front/static/ts/views/RegisterPage.ts @@ -16,7 +16,7 @@ export default class extends Aview { - + i already have an account @@ -24,4 +24,40 @@ export default class extends Aview {
`; } + + async run() { + const login = async () => { + const username = (document.getElementById("username") as HTMLInputElement).value; + const password = (document.getElementById("password") as HTMLInputElement).value; + + try { + const response = await fetch("http://localhost:3001/register", { + method: "POST", + headers: { "Content-Type": "application/json", }, + credentials: "include", + body: JSON.stringify({ user: username, password: password }), + }); + const data = await response.json(); + + if (response.status === 200) + { + navigationManager("/"); + } + else if (response.status === 400) + { + document.getElementById("login-error-message").innerHTML = "error: " + data.error; + document.getElementById("login-error-message").classList.remove("hidden"); + } + + } + catch (error) + { + console.log(error); + document.getElementById("login-error-message").innerHTML = "error: server error, try again later..."; + document.getElementById("login-error-message").classList.remove("hidden"); + } + }; + + document.getElementById("register-button")?.addEventListener("click", login); + } } From 62d4c7df6e236bfb24f73c36ec2daaa5f100bb57 Mon Sep 17 00:00:00 2001 From: Tzvetan Trave Date: Mon, 29 Sep 2025 19:43:13 +0200 Subject: [PATCH 015/109] added doc --- doc/user/user.md | 408 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 doc/user/user.md diff --git a/doc/user/user.md b/doc/user/user.md new file mode 100644 index 0000000..cdc5779 --- /dev/null +++ b/doc/user/user.md @@ -0,0 +1,408 @@ +# User + +Available endpoints: +- POST `/users/:userId` +- POST `/users/:userId/friends` +- POST `/users/:userId/matchHistory` +- GET `/users` +- GET `/users/:userId` +- GET `/users/:userId/friends` +- GET `/users/:userId/matchHistory` +- PATCH `/users/:userId/:member` +- DELETE `/users/:userId` +- DELETE `/users/:userId/:member` +- DELETE `/users/:userId/friends` +- DELETE `/users/:userId/friends/:friendId` +- DELETE `/users/:userId/matchHistory` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## POST `/users/:userId` + +Used to create an user + +Input needed : +```json +{ + "displayName": "" +} +``` + +Can return: +- 200 with response +```json +{ + "msg": "User created successfully" +} +``` +- 400 with response (if no user is specified in header, or user already exists, or no display name is specified in body) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is not admin) +```json +{ + "error": "" +} +``` + +## POST `/users/:userId/friends` + +Used to add a friend + +Input needed : +```json +{ + "friend": "" +} +``` + +Can return: +- 200 with response +```json +{ + "msg": "Friend added successfully" +} +``` +- 400 with response (if no user is specified in header, or friend is the user specified in header, or no friend is specified in body) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist, or friend does not exist) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` + +## POST `/users/:userId/matchHistory` + +Used to add a match result + +Input needed : +```json +{ + "opponent": "", + "p1Score": , + "p2Score": +} +``` + +Can return: +- 200 with response +```json +{ + "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) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist, or opponent does not exist) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` + +## GET `/users` + +Used to get the user list + +Always returns: +- 200 with response (list of user objects) +```json +[ + { + "username": "", + "displayName": "", + "wins": , + "losses": + }, + ... +] +``` + +## GET `/users/:userId` + +Used to get an user + +Can return: +- 200 with response (an user object) +```json +{ + "username": "", + "displayName": "", + "wins": , + "losses": +} +``` +- 404 with response (if user does not exist) +```json +{ + "error": "" +} +``` + +## GET `/users/:userId/friends` + +Used to the friends of a user + +Can return: +- 200 with response (list of friend objects) +```json +[ + { + "friendName": "" + }, + ... +] +``` +- 404 with response (if user does not exist, or user does not have friends) +```json +{ + "error": "" +} +``` + +## GET `/users/:userId/matchHistory` + +Used to the match history of a user + +Input needed : +```json +{ + "iStart": , + "iEnd": +} +``` + +Can return: +- 200 with response (list of matches results (between iStart and iEnd)) +```json +[ + { + "score": + { + "p1": "", + "p2": "", + "p1Score": "", + "p2Score": "" + }, + "tx": "" + }, + ... +] +``` +- 400 with response (if iStart/iEnd does not exist, or iEnd < iStart) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist, or user did not play any matches) +```json +{ + "error": "" +} +``` + +## PATCH `/users/:userId/:member` + +Used to modify the member of a user (only displayName can be modified) + +Input needed : +```json +{ + "": "" +} +``` + +Can return: +- 200 with response +```json +{ + "msg": "<:member> modified sucessfully" +} +``` +- 400 with response (if no user is specified in header, or new value of member to modify is not provided in the body, or member does not exist) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is not admin) +```json +{ + "error": "" +} +``` + +## DELETE `/users/:userId` + +Used to delete a user + +Can return: +- 200 with response +```json +{ + "msg": "User deleted successfully" +} +``` +- 404 with response (user does not exist) +```json +{ + "error": "" +} +``` + +## DELETE `/users/:userId/:member` + +Used to delete a member (only displayName can be deleted) + +Can return: +- 200 with response +```json +{ + "msg": "<:member> deleted successfully" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 400 with response (if no user is specified in header, or member to delete does not exist) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist) +```json +{ + "error": "" +} +``` + +## DELETE `/users/:userId/friends` + +Used to delete friends + +Can return: +- 200 with response +```json +{ + "msg": "Friends deleted successfully" +} +``` +- 400 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist) +```json +{ + "error": "" +} +``` + +## DELETE `/users/:userId/friends/:friendId` + +Used to delete a friend + +Can return: +- 200 with response +```json +{ + "msg": "Friend deleted successfully" +} +``` +- 400 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist, or friend does not exist) +```json +{ + "error": "" +} +``` + +## DELETE `/users/:userId/matchHistory` + +Used to delete the match history + +Can return: +- 200 with response +```json +{ + "msg": "Match history deleted successfully" +} +``` +- 400 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 401 with response (if user specified in header is neither admin nor user) +```json +{ + "error": "" +} +``` +- 404 with response (if user does not exist) +```json +{ + "error": "" +} +``` From db5858a344655b6b9c9b4dbcb57cc90f88fb6151 Mon Sep 17 00:00:00 2001 From: Adam <45126464+KeyZox71@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:35:25 +0200 Subject: [PATCH 016/109] les crampte la XD --- doc/auth/me.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/auth/me.md b/doc/auth/me.md index 4c907e2..26a2e15 100644 --- a/doc/auth/me.md +++ b/doc/auth/me.md @@ -3,3 +3,9 @@ GET `/me` Inputs : just need the JWT cookie Returns the user of the account + +``` +{ + user: ":userId" +} +``` From 59bd580136637b0fdc1ca86329d3378409022629 Mon Sep 17 00:00:00 2001 From: y-syo Date: Tue, 30 Sep 2025 16:05:15 +0200 Subject: [PATCH 017/109] =?UTF-8?q?=E3=80=8C=F0=9F=8F=97=EF=B8=8F=E3=80=8D?= =?UTF-8?q?=20wip:=20work=20in=20progress,=20not=20done=20yet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/front/index.html | 2 +- src/front/static/ts/main.ts | 44 +++++++++++++++++++++-- src/front/static/ts/views/Game.ts | 18 ++++++++-- src/front/static/ts/views/LoginPage.ts | 16 ++++++--- src/front/static/ts/views/RegisterPage.ts | 25 +++++++++---- 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/front/index.html b/src/front/index.html index 97c4aea..0e7465d 100644 --- a/src/front/index.html +++ b/src/front/index.html @@ -16,7 +16,7 @@
diff --git a/src/front/static/ts/main.ts b/src/front/static/ts/main.ts index 8ce2cc8..910e75e 100644 --- a/src/front/static/ts/main.ts +++ b/src/front/static/ts/main.ts @@ -1,4 +1,41 @@ -const navigationManager = url => { +export async function isLogged(): boolean { + let uuid_req = await fetch("http://localhost:3001/me", { + method: "GET", + credentials: "include", + }); + if (uuid_req.status == 200) + { + let uuid = await uuid_req.json(); + document.cookie = `uuid=${uuid.user};max-age=${60*60*24*7}`; + + const old_button = document.getElementById("profile-button"); + const logged_dropdown = document.createElement("button"); + logged_dropdown.innerHTML = "logged in, more like locked in ahah i'm gonna kill myself the 12th of October"; + logged_dropdown.classList.add("text-neutral-900", "hover:text-neutral-700", "dark:text-white", "dark:hover:text-neutral-400"); + logged_dropdown.id = "profile-button"; + + // add the dropdown button and the dropdown logic + + old_button.replaceWith(logged_dropdown); + return true; + } + else // 401 + { + document.cookie = `uuid=;max-age=0`; + const old_button = document.getElementById("profile-button"); + const login_button = document.createElement("a"); + login_button.id = "profile-button"; + login_button.text = "login"; + login_button.classList.add("text-neutral-900", "hover:text-neutral-700", "dark:text-white", "dark:hover:text-neutral-400"); + login_button.href = "/login"; + login_button.setAttribute("data-link", ""); + + old_button.replaceWith(login_button); + return false; + } +} + +export const navigationManager = url => { history.pushState(null, null, url); router(); }; @@ -14,6 +51,8 @@ const routes = [ { path: "/login", view: () => import("./views/LoginPage.ts") }, { path: "/register", view: () => import("./views/RegisterPage.ts") }, + + { path: "/tetris", view: () => import("./views/Tetris.ts") }, ]; const router = async () => { @@ -30,7 +69,7 @@ const router = async () => { if (view) view.running = false; - console.log(match); + //console.log(match); const module = await match.route.view(); view = new module.default(); @@ -42,6 +81,7 @@ const router = async () => { window.addEventListener("popstate", router); document.addEventListener("DOMContentLoaded", () => { + isLogged(); document.body.addEventListener("click", e=> { if (e.target.matches("[data-link]")) diff --git a/src/front/static/ts/views/Game.ts b/src/front/static/ts/views/Game.ts index 3bd39b5..b814e1b 100644 --- a/src/front/static/ts/views/Game.ts +++ b/src/front/static/ts/views/Game.ts @@ -59,9 +59,9 @@ export default class extends Aview { document.addEventListener("keyup", e => { keys[e.key] = false; }); function movePaddles() { - if (keys["w"] && leftPaddleY > 0) + if ((keys["w"] || keys["W"]) && leftPaddleY > 0) leftPaddleY -= paddleSpeed * elapsed; - if (keys["s"] && leftPaddleY < canvas.height - paddleHeight) + if ((keys["s"] || keys["S"]) && leftPaddleY < canvas.height - paddleHeight) leftPaddleY += paddleSpeed * elapsed; if (keys["ArrowUp"] && rightPaddleY > 0) rightPaddleY -= paddleSpeed * elapsed; @@ -116,7 +116,14 @@ export default class extends Aview { p1_score++; if (p1_score === 3 || p2_score === 3) + { + // ------------------------------------------------------------------------------------------------------------------------------------------ + // + // insert the fetch to the ScoreStore api here + // + // ------------------------------------------------------------------------------------------------------------------------------------------ match_over = true; + } else { countdown = 3; @@ -191,7 +198,6 @@ export default class extends Aview { moveBall(); } draw(); - console.log(game_playing); if (!game_playing) startCountdown(); if (this.running) @@ -210,6 +216,12 @@ export default class extends Aview { countdownTimer = performance.now(); }); + // -------------------------------------------------------------------------------------------------------------------------------------------------------- + // + // insert logic to set both names + // + // -------------------------------------------------------------------------------------------------------------------------------------------------------- + requestAnimationFrame(gameLoop); } } diff --git a/src/front/static/ts/views/LoginPage.ts b/src/front/static/ts/views/LoginPage.ts index c8dca62..fd7ba3e 100644 --- a/src/front/static/ts/views/LoginPage.ts +++ b/src/front/static/ts/views/LoginPage.ts @@ -1,4 +1,5 @@ import Aview from "./Aview.ts" +import { isLogged, navigationManager } from "../main.ts" export default class extends Aview { @@ -31,28 +32,33 @@ export default class extends Aview { const password = (document.getElementById("password") as HTMLInputElement).value; try { - const response = await fetch("http://localhost:3001/login", { + const data_req = await fetch("http://localhost:3001/login", { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ user: username, password: password }), }); - const data = await response.json(); + const data = await data_req.json(); - if (response.status === 200) + if (data_req.status === 200) { + isLogged(); navigationManager("/"); } - else if (response.status === 400) + else if (data_req.status === 400) { document.getElementById("login-error-message").innerHTML = "error: " + data.error; document.getElementById("login-error-message").classList.remove("hidden"); } + else + { + throw new Error("invalid response"); + } } catch (error) { - console.log(error); + console.error(error); document.getElementById("login-error-message").innerHTML = "error: server error, try again later..."; document.getElementById("login-error-message").classList.remove("hidden"); } diff --git a/src/front/static/ts/views/RegisterPage.ts b/src/front/static/ts/views/RegisterPage.ts index 13fdf55..0701662 100644 --- a/src/front/static/ts/views/RegisterPage.ts +++ b/src/front/static/ts/views/RegisterPage.ts @@ -1,4 +1,5 @@ import Aview from "./Aview.ts" +import { isLogged, navigationManager } from "../main.ts" export default class extends Aview { @@ -15,7 +16,7 @@ export default class extends Aview { - + @@ -31,28 +32,40 @@ export default class extends Aview { const password = (document.getElementById("password") as HTMLInputElement).value; try { - const response = await fetch("http://localhost:3001/register", { + const data_req = await fetch("http://localhost:3001/register", { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ user: username, password: password }), }); - const data = await response.json(); + const data = await data_req.json(); - if (response.status === 200) + if (data_req.status === 200) { + let uuid_req = await fetch("http://localhost:3001/me", { + method: "GET", + credentials: "include", + }); + let uuid = await uuid_req.json(); + document.cookie = `uuid=${uuid.user};max-ages=${60*60*24*7}`; + console.log(document.cookie); + isLogged(); navigationManager("/"); } - else if (response.status === 400) + else if (data_req.status === 400) { document.getElementById("login-error-message").innerHTML = "error: " + data.error; document.getElementById("login-error-message").classList.remove("hidden"); } + else + { + throw new Error("invalid response"); + } } catch (error) { - console.log(error); + console.error(error); document.getElementById("login-error-message").innerHTML = "error: server error, try again later..."; document.getElementById("login-error-message").classList.remove("hidden"); } From ae4838166be24eedee63dac2c645b110d05566fc Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 1 Oct 2025 19:44:48 +0200 Subject: [PATCH 018/109] =?UTF-8?q?=E3=80=8C=E2=9C=A8=E3=80=8D=20feat(auth?= =?UTF-8?q?-api):=20/logout=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/default.js | 3 +++ src/api/auth/logout.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/api/auth/logout.js diff --git a/src/api/auth/default.js b/src/api/auth/default.js index 6a15651..78e7411 100644 --- a/src/api/auth/default.js +++ b/src/api/auth/default.js @@ -10,6 +10,7 @@ import { gRegisterCallback } from './gRegisterCallback.js'; import { totpSetup } from './totpSetup.js'; import { totpDelete } from './totpDelete.js'; import { totpVerify } from './totpVerify.js'; +import { logout } from './logout.js'; const saltRounds = 10; export const appName = process.env.APP_NAME || 'knl_meowscendence'; @@ -107,4 +108,6 @@ export default async function(fastify, options) { } } }, async (request, reply) => { return register(request, reply, saltRounds, fastify); }); + + fastify.get('/logout', {}, async (request, reply) => { return logout(request, reply, fastify); }) } diff --git a/src/api/auth/logout.js b/src/api/auth/logout.js new file mode 100644 index 0000000..d4117b8 --- /dev/null +++ b/src/api/auth/logout.js @@ -0,0 +1,17 @@ +/** + * @async + * @param {import("fastify").FastifyReply} reply + * + * @returns {import("fastify").FastifyReply} + */ +export async function logout(reply) { + try { + return reply + .code(200) + .clearCookie() + .send({ msg: "Logout successful" }); + } catch { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } +} From e23922d4e3441ccb7b74df9d53a483e3c0a613e5 Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 1 Oct 2025 19:50:03 +0200 Subject: [PATCH 019/109] =?UTF-8?q?=E3=80=8C=F0=9F=93=9D=E3=80=8D=20doc(au?= =?UTF-8?q?th-api):=20logout=20doc=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/auth/logout.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 doc/auth/logout.md diff --git a/doc/auth/logout.md b/doc/auth/logout.md new file mode 100644 index 0000000..98b6edc --- /dev/null +++ b/doc/auth/logout.md @@ -0,0 +1,24 @@ +# Logout + +Available endpoints: +- GET `/logout` + +Common return: +- 500 with response +```json +{ + "error": "Internal server error" +} +``` + +## GET `/logout` + +Used to logout the client (it just delete the cookie) + +Returns: +- 200 with response and clear cookie +```json +{ + "msg": "Logout successful" +} +``` From 544289e2af9f88ae64282ee85f1fc91d3482edc8 Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 1 Oct 2025 20:11:27 +0200 Subject: [PATCH 020/109] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix:?= =?UTF-8?q?=20fixed=20a=20massive=20skill=20issue=20but=20f*ck=20js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/default.js | 2 +- src/api/auth/logout.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/auth/default.js b/src/api/auth/default.js index 78e7411..0e491ac 100644 --- a/src/api/auth/default.js +++ b/src/api/auth/default.js @@ -109,5 +109,5 @@ export default async function(fastify, options) { } }, async (request, reply) => { return register(request, reply, saltRounds, fastify); }); - fastify.get('/logout', {}, async (request, reply) => { return logout(request, reply, fastify); }) + fastify.get('/logout', {}, async (request, reply) => { return logout(reply, fastify); }) } diff --git a/src/api/auth/logout.js b/src/api/auth/logout.js index d4117b8..84d1d22 100644 --- a/src/api/auth/logout.js +++ b/src/api/auth/logout.js @@ -1,10 +1,11 @@ /** * @async * @param {import("fastify").FastifyReply} reply + * @param {import("fastify").FastifyInstance} fastify * * @returns {import("fastify").FastifyReply} */ -export async function logout(reply) { +export async function logout(reply, fastify) { try { return reply .code(200) From 705571ee120e2f86ade38b2c8768211867aa555c Mon Sep 17 00:00:00 2001 From: adjoly Date: Wed, 1 Oct 2025 20:16:58 +0200 Subject: [PATCH 021/109] =?UTF-8?q?=E3=80=8C=F0=9F=94=A8=E3=80=8D=20fix:?= =?UTF-8?q?=20fixed=20some=20things.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/logout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/auth/logout.js b/src/api/auth/logout.js index 84d1d22..b3c57ef 100644 --- a/src/api/auth/logout.js +++ b/src/api/auth/logout.js @@ -9,7 +9,7 @@ export async function logout(reply, fastify) { try { return reply .code(200) - .clearCookie() + .clearCookie("token") .send({ msg: "Logout successful" }); } catch { fastify.log.error(err); From 35f5df492496e74a6ded1f57a19fc95318bdd31a Mon Sep 17 00:00:00 2001 From: y-syo Date: Wed, 1 Oct 2025 22:12:50 +0200 Subject: [PATCH 022/109] =?UTF-8?q?=E3=80=8C=F0=9F=9A=A7=E3=80=8D=20test(s?= =?UTF-8?q?rc/front):=20testing=20things,=20might=20broke.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/front/static/ts/main.ts | 47 +++++++++++++++++++---- src/front/static/ts/views/Game.ts | 32 ++++++++++----- src/front/static/ts/views/LoginPage.ts | 8 ++-- src/front/static/ts/views/RegisterPage.ts | 8 ++-- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/front/static/ts/main.ts b/src/front/static/ts/main.ts index 910e75e..dd8847a 100644 --- a/src/front/static/ts/main.ts +++ b/src/front/static/ts/main.ts @@ -3,20 +3,53 @@ export async function isLogged(): boolean { method: "GET", credentials: "include", }); - if (uuid_req.status == 200) + if (uuid_req.status === 200) { let uuid = await uuid_req.json(); document.cookie = `uuid=${uuid.user};max-age=${60*60*24*7}`; const old_button = document.getElementById("profile-button"); - const logged_dropdown = document.createElement("button"); - logged_dropdown.innerHTML = "logged in, more like locked in ahah i'm gonna kill myself the 12th of October"; - logged_dropdown.classList.add("text-neutral-900", "hover:text-neutral-700", "dark:text-white", "dark:hover:text-neutral-400"); - logged_dropdown.id = "profile-button"; - // add the dropdown button and the dropdown logic + const dropdown = document.createElement("div"); + dropdown.classList.add("relative", "inline-block", "group"); + dropdown.id = "profile-button"; + const button_dropdown = dropdown.appendChild(document.createElement("button")); + button_dropdown.innerHTML = uuid.user; + button_dropdown.classList.add("text-neutral-900", "group-hover:text-neutral-700", "dark:text-white", "dark:group-hover:text-neutral-400"); - old_button.replaceWith(logged_dropdown); + const menu_div = dropdown.appendChild(document.createElement("div")); + menu_div.classList.add("float:right", "hidden", "absolute", "right-0", "bg-[#f9f9f9]", "min-w-[160px]", "shadow-lg", "z-10", "group-hover:block"); + + const profile_a = menu_div.appendChild(document.createElement("a")); + const settings_a = menu_div.appendChild(document.createElement("a")); + const logout_button = menu_div.appendChild(document.createElement("button")); + + profile_a.text = "profile"; + profile_a.classList.add("block", "no-underline", "px-4", "py-3"); + profile_a.href = "/profile"; + profile_a.setAttribute("data-link", ""); + + settings_a.text = "settings"; + settings_a.classList.add("block", "no-underline", "px-4", "py-3"); + settings_a.href = "/settings"; + settings_a.setAttribute("data-link", ""); + + logout_button.innerHTML = "logout"; + logout_button.classList.add("block", "no-underline", "px-4", "py-3"); + logout_button.id = "logout-button"; + //document.getElementById("logout-button")?.addEventListener("click", async () => { + logout_button.addEventListener("click", async () => { + let req = await fetch("http://localhost:3001/logout", { + method: "GET", + credentials: "include", + }); + if (req.status === 200) + isLogged(); + else + console.error("logout failed"); + }); + + old_button.replaceWith(dropdown); return true; } else // 401 diff --git a/src/front/static/ts/views/Game.ts b/src/front/static/ts/views/Game.ts index b814e1b..41fe224 100644 --- a/src/front/static/ts/views/Game.ts +++ b/src/front/static/ts/views/Game.ts @@ -1,4 +1,5 @@ import Aview from "./Aview.ts" +import { isLogged } from "../main.js" export default class extends Aview { @@ -13,10 +14,13 @@ export default class extends Aview { async getHTML() { return ` - -
- -