From e9a61f17161bda962a0346d2d8e338cef407a434 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 14:17:33 +0100 Subject: [PATCH 01/66] (misc): Initial commit --- src/tic-tac-toe/README.md | 6 ++++++ src/tic-tac-toe/package.json | 17 +++++++++++++++++ src/tic-tac-toe/tsconfig.json | 5 +++++ 3 files changed, 28 insertions(+) create mode 100644 src/tic-tac-toe/README.md create mode 100644 src/tic-tac-toe/package.json create mode 100644 src/tic-tac-toe/tsconfig.json diff --git a/src/tic-tac-toe/README.md b/src/tic-tac-toe/README.md new file mode 100644 index 0000000..bdf6589 --- /dev/null +++ b/src/tic-tac-toe/README.md @@ -0,0 +1,6 @@ +# Anatomy of a microservice + +# Backend + +# Frontend + diff --git a/src/tic-tac-toe/package.json b/src/tic-tac-toe/package.json new file mode 100644 index 0000000..2ae85bd --- /dev/null +++ b/src/tic-tac-toe/package.json @@ -0,0 +1,17 @@ +{ + "name": "tic-tac-toe", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "npm run build && node dist/run.js", + "build": "vite build", + "build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false", + "build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && node dist/openapi.cjs >openapi.json", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.24.0" +} diff --git a/src/tic-tac-toe/tsconfig.json b/src/tic-tac-toe/tsconfig.json new file mode 100644 index 0000000..cd65905 --- /dev/null +++ b/src/tic-tac-toe/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": {}, + "include": ["src/**/*.ts"] +} \ No newline at end of file From c7149cffad197b52837e5b7457028932e566ad99 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:05:49 +0100 Subject: [PATCH 02/66] (misc): Started working on entrypoint for the microservice. --- src/tic-tac-toe/src/run.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/tic-tac-toe/src/run.ts diff --git a/src/tic-tac-toe/src/run.ts b/src/tic-tac-toe/src/run.ts new file mode 100644 index 0000000..2b8a619 --- /dev/null +++ b/src/tic-tac-toe/src/run.ts @@ -0,0 +1,30 @@ +// @file run.ts + +// Entry point of the microservice, ran by the Dockerfile. + +import fastify, { FastifyInstance } from 'fastify'; +// TODO: Import the microservice app + + +// @brief Entrypoint for the microservice's backend. +const start = async () => { + // TODO: Thingies to send to log service (if I understood that correctly from /src/chat/src/run.ts) + + // TODO: Add the logging thingy to the call to fastify() + const fastInst: FastifyInstance = fastify(); + try { + process.on('SIGTERM', () => { + fastInst.log.info('Requested to shutdown'); + process.exit(143); + }); + // TODO: Uncomment when app.ts will be import-able. + // await fastInst.register(app); + await fastInst.listen({ port: 80, host: '0.0.0.0' }); + } + catch (err) { + fastInst.log.error(err); + process.exit(1); + }; +}; + +start(); \ No newline at end of file From fe52fbbbd44559c40e44eea10452096a810bfe79 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:17:24 +0100 Subject: [PATCH 03/66] (misc): Started working on app.ts. --- src/tic-tac-toe/src/app.ts | 198 +++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 src/tic-tac-toe/src/app.ts diff --git a/src/tic-tac-toe/src/app.ts b/src/tic-tac-toe/src/app.ts new file mode 100644 index 0000000..ca5fa57 --- /dev/null +++ b/src/tic-tac-toe/src/app.ts @@ -0,0 +1,198 @@ +import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify'; +// TODO: Import Fastify formbody +// TODO: Import Fastify multipart +// TODO: Import shared database +// TODO: Import shared auth +// TODO: Import shared swagger +// TODO: Import shared utils +// TODO: Import socketio + +// @brief ??? +declare const __SERVICE_NAME: string; + +// TODO: Import the plugins defined for this microservice +// TODO: Import the routes defined for this microservice + +// @brief The microservice app (as a plugin for Fastify), kinda like a main function I guess ??? +// @param fastify +// @param opts +export const app: FastifyPluginAsync = async (fastify, opts): Promise => { + // Register all the fastify plugins that this app will use + + // Once it is done: + fastify.ready((err) => { + if (err) { + throw err; + } + // TODO: Supposedly, something should be there I guess + }); +}; +// Export it as the default for this file. +export default app; + +// TODO: Understand what is this for in /src/chat/src/app.ts +// declare module 'fastify' { +// interface FastifyInstance { +// io: Server<{ +// hello: (message: string) => string; +// MsgObjectServer: (data: { message: ClientMessage }) => void; +// message: (msg: string) => void; +// testend: (sock_id_client: string) => void; +// }>; +// } +// } + +// TODO: Same for this, also in /src/chat/src/app.ts +// async function onReady(fastify: FastifyInstance) { +// function connectedUser(io?: Server, target?: string): number { +// let count = 0; +// const seen = new Set(); +// // <- only log/count unique usernames + +// for (const [socketId, username] of clientChat) { +// // Basic sanity checks +// if (typeof socketId !== 'string' || socketId.length === 0) { +// clientChat.delete(socketId); +// continue; +// } +// if (typeof username !== 'string' || username.length === 0) { +// clientChat.delete(socketId); +// continue; +// } + +// // If we have the io instance, attempt to validate the socket is still connected +// if (io && typeof io.sockets?.sockets?.get === 'function') { +// const s = io.sockets.sockets.get(socketId) as +// | Socket +// | undefined; +// // If socket not found or disconnected, remove from map and skip +// if (!s || s.disconnected) { +// clientChat.delete(socketId); +// continue; +// } + +// // Skip duplicates (DO NOT delete them — just don't count) +// if (seen.has(username)) { +// continue; +// } +// // socket exists and is connected +// seen.add(username); +// count++; +// // console.log(color.green,"count: ", count); +// console.log(color.yellow, 'Client:', color.reset, username); + +// const targetSocketId = target; +// io.to(targetSocketId!).emit('listObj', username); + +// console.log( +// color.yellow, +// 'Chat Socket ID:', +// color.reset, +// socketId, +// ); +// continue; +// } + +// // If no io provided, assume entries in the map are valid and count them. +// count++; +// console.log( +// color.red, +// 'Client (unverified):', +// color.reset, +// username, +// ); +// console.log( +// color.red, +// 'Chat Socket ID (unverified):', +// color.reset, +// socketId, +// ); +// } + +// return count; +// } + +// function broadcast(data: ClientMessage, sender?: string) { +// fastify.io.fetchSockets().then((sockets) => { +// for (const s of sockets) { +// if (s.id !== sender) { +// // Send REAL JSON object +// const clientName = clientChat.get(s.id) || null; +// if (clientName !== null) { +// s.emit('MsgObjectServer', { message: data }); +// } +// console.log(' Target window socket ID:', s.id); +// console.log(' Target window ID:', [...s.rooms]); +// console.log(' Sender window ID:', sender ? sender : 'none'); +// } +// } +// }); +// } + +// fastify.io.on('connection', (socket: Socket) => { +// socket.on('message', (message: string) => { +// console.info( +// color.blue, +// 'Socket connected!', +// color.reset, +// socket.id, +// ); +// console.log( +// color.blue, +// 'Received message from client', +// color.reset, +// message, +// ); + +// const obj: ClientMessage = JSON.parse(message) as ClientMessage; +// clientChat.set(socket.id, obj.user); +// console.log( +// color.green, +// 'Message from client', +// color.reset, +// `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, +// ); +// // Send object directly — DO NOT wrap it in a string +// broadcast(obj, obj.SenderWindowID); +// console.log( +// color.red, +// 'connected in the Chat :', +// connectedUser(fastify.io), +// color.reset, +// ); +// }); + +// socket.on('testend', (sock_id_cl: string) => { +// console.log('testend received from client socket id:', sock_id_cl); +// }); + +// socket.on('list', () => { +// console.log(color.red, 'list activated', color.reset, socket.id); +// connectedUser(fastify.io, socket.id); +// }); + +// socket.on('disconnecting', (reason) => { +// const clientName = clientChat.get(socket.id) || null; +// console.log( +// color.green, +// `Client disconnecting: ${clientName} (${socket.id}) reason:`, +// reason, +// ); +// if (reason === 'transport error') return; + +// if (clientName !== null) { +// const obj = { +// type: 'chat', +// user: clientName, +// token: '', +// text: 'LEFT the chat', +// timestamp: Date.now(), +// SenderWindowID: socket.id, +// }; + +// broadcast(obj, obj.SenderWindowID); +// // clientChat.delete(obj.user); +// } +// }); +// }); +// } \ No newline at end of file From 8e3413fb95bd8f1924e485c7b429739504663eba Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:20:13 +0100 Subject: [PATCH 04/66] (misc): Added configuration files. --- src/tic-tac-toe/.dockerignore | 2 ++ src/tic-tac-toe/entrypoint.sh | 7 +++++ src/tic-tac-toe/vite.config.js | 53 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/tic-tac-toe/.dockerignore create mode 100644 src/tic-tac-toe/entrypoint.sh create mode 100644 src/tic-tac-toe/vite.config.js diff --git a/src/tic-tac-toe/.dockerignore b/src/tic-tac-toe/.dockerignore new file mode 100644 index 0000000..246d599 --- /dev/null +++ b/src/tic-tac-toe/.dockerignore @@ -0,0 +1,2 @@ +/dist +/node_modules \ No newline at end of file diff --git a/src/tic-tac-toe/entrypoint.sh b/src/tic-tac-toe/entrypoint.sh new file mode 100644 index 0000000..91a963d --- /dev/null +++ b/src/tic-tac-toe/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e +set -x + +# run the CMD [ ... ] from the dockerfile +exec "$@" diff --git a/src/tic-tac-toe/vite.config.js b/src/tic-tac-toe/vite.config.js new file mode 100644 index 0000000..48ad3fa --- /dev/null +++ b/src/tic-tac-toe/vite.config.js @@ -0,0 +1,53 @@ +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import nodeExternals from 'rollup-plugin-node-externals'; +import path from 'node:path'; +import fs from 'node:fs'; + +function collectDeps(...pkgJsonPaths) { + const allDeps = new Set(); + for (const pkgPath of pkgJsonPaths) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + for (const dep of Object.keys(pkg.dependencies || {})) { + allDeps.add(dep); + } + for (const peer of Object.keys(pkg.peerDependencies || {})) { + allDeps.add(peer); + } + } + return Array.from(allDeps); +}; + +const externals = collectDeps( + './package.json', + '../@shared/package.json', +); + +export default defineConfig({ + root: __dirname, + define: { + __SERVICE_NAME: '"chat"', + }, + // service root + plugins: [tsconfigPaths(), nodeExternals()], + build: { + ssr: true, + outDir: 'dist', + emptyOutDir: true, + lib: { + entry: path.resolve(__dirname, process.env.VITE_ENTRYPOINT ?? 'src/run.ts'), + // adjust main entry + formats: ['cjs'], + // CommonJS for Node.js + fileName: () => 'index.js', + }, + rollupOptions: { + external: externals, + }, + target: 'node22', + // or whatever Node version you use + sourcemap: true, + minify: false, + // for easier debugging + }, +}); From 69809e284e3a8529fb0996c1ae430c6b3f6042da Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:22:42 +0100 Subject: [PATCH 05/66] (eslint): Removed trailing space at end of line 2. --- src/tic-tac-toe/src/run.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tic-tac-toe/src/run.ts b/src/tic-tac-toe/src/run.ts index 2b8a619..3707382 100644 --- a/src/tic-tac-toe/src/run.ts +++ b/src/tic-tac-toe/src/run.ts @@ -1,8 +1,10 @@ // @file run.ts +// @brief The entrypoint to the service. // Entry point of the microservice, ran by the Dockerfile. import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; // TODO: Import the microservice app @@ -18,7 +20,7 @@ const start = async () => { process.exit(143); }); // TODO: Uncomment when app.ts will be import-able. - // await fastInst.register(app); + await fastInst.register(app); await fastInst.listen({ port: 80, host: '0.0.0.0' }); } catch (err) { From 8b915058946399a035d859244c4d7c09436f94ca Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:25:31 +0100 Subject: [PATCH 06/66] (eslint): Removed empty line. --- src/tic-tac-toe/src/run.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tic-tac-toe/src/run.ts b/src/tic-tac-toe/src/run.ts index 3707382..d49db36 100644 --- a/src/tic-tac-toe/src/run.ts +++ b/src/tic-tac-toe/src/run.ts @@ -7,7 +7,6 @@ import fastify, { FastifyInstance } from 'fastify'; import app from './app'; // TODO: Import the microservice app - // @brief Entrypoint for the microservice's backend. const start = async () => { // TODO: Thingies to send to log service (if I understood that correctly from /src/chat/src/run.ts) From 9734e2d8656da2afbd60d3d5f73376056bb6765e Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 15:33:05 +0100 Subject: [PATCH 07/66] (docker-compose): Added tic-tac-toe service to the docker-compose file. --- docker-compose.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 1b166ef..b6ac51e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,6 +101,25 @@ services: gelf-address: "udp://127.0.0.1:12201" tag: "{{.Name}}" + ############### + # TIC-TAC-TOE # + ############### + tic-tac-toe: + build: + context: ./src/ + args: + - SERVICE=tic-tac-toe + - EXTRA_FILES=tic-tac-toe/extra + container_name: tic-tac-toe + restart: unless-stopped + networks: + - transcendance-network + volumes: + - sqlite-volume:/volumes/database + - static-volume:/volumes/static + environment: + - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA + - DATABASE_DIR=/volumes/database ############### # CHAT # From 56ad511ceb74d6507c284a31788c3fb057a33013 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 17:53:05 +0100 Subject: [PATCH 08/66] (punition): No more working on backend until Maieul says so :( --- docker-compose.yml | 32 ++++++++++++++++---------------- src/tic-tac-toe/README.md | 16 ++++++++++++++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b6ac51e..161cbb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -104,22 +104,22 @@ services: ############### # TIC-TAC-TOE # ############### - tic-tac-toe: - build: - context: ./src/ - args: - - SERVICE=tic-tac-toe - - EXTRA_FILES=tic-tac-toe/extra - container_name: tic-tac-toe - restart: unless-stopped - networks: - - transcendance-network - volumes: - - sqlite-volume:/volumes/database - - static-volume:/volumes/static - environment: - - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA - - DATABASE_DIR=/volumes/database + # tic-tac-toe: + # build: + # context: ./src/ + # args: + # - SERVICE=tic-tac-toe + # - EXTRA_FILES=tic-tac-toe/extra + # container_name: tic-tac-toe + # restart: unless-stopped + # networks: + # - transcendance-network + # volumes: + # - sqlite-volume:/volumes/database + # - static-volume:/volumes/static + # environment: + # - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA + # - DATABASE_DIR=/volumes/database ############### # CHAT # diff --git a/src/tic-tac-toe/README.md b/src/tic-tac-toe/README.md index bdf6589..e8ef203 100644 --- a/src/tic-tac-toe/README.md +++ b/src/tic-tac-toe/README.md @@ -1,6 +1,18 @@ +# Directory layout +```plaintext +./src/tic-tac-toe +├── entrypoint.sh +├── package.json +├── README.md +├── src +│ ├── app.ts # The microservice app file, where the major part of the backend code lives. +│ └── run.ts # Equivalent of server.ts, it is the entrypoint for our service. +├── tsconfig.json +└── vite.config.js +``` + # Anatomy of a microservice # Backend -# Frontend - +# Frontend \ No newline at end of file From 7f412c8fb5bba1b851754f4f66247b6a83eb7fc7 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 4 Dec 2025 17:59:34 +0100 Subject: [PATCH 09/66] (feat): Maieul helped starting the frontend. --- frontend/src/pages/index.ts | 1 + frontend/src/pages/ttt/README.md | 3 + frontend/src/pages/ttt/ttt.html | 13 +++ frontend/src/pages/ttt/ttt.ts | 137 +++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 frontend/src/pages/ttt/README.md create mode 100644 frontend/src/pages/ttt/ttt.html create mode 100644 frontend/src/pages/ttt/ttt.ts diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index 48e2f55..5ad7a75 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -3,6 +3,7 @@ import './root/root.ts' import './chat/chat.ts' import './login/login.ts' import './signin/signin.ts' +import './ttt/ttt.ts' // ---- Initial load ---- setTitle(""); diff --git a/frontend/src/pages/ttt/README.md b/frontend/src/pages/ttt/README.md new file mode 100644 index 0000000..73001a2 --- /dev/null +++ b/frontend/src/pages/ttt/README.md @@ -0,0 +1,3 @@ +# TO-DO +For now I am prohibited from working on the backend as per Maieul's request. +[ ] - Task for December 08 or 09: Tic-tac-toe should lock up once it's finished (i.e., draw or win) and print who won, how many turns and which row/col/diag the win happened in \ No newline at end of file diff --git a/frontend/src/pages/ttt/ttt.html b/frontend/src/pages/ttt/ttt.html new file mode 100644 index 0000000..48e7efa --- /dev/null +++ b/frontend/src/pages/ttt/ttt.html @@ -0,0 +1,13 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts new file mode 100644 index 0000000..e34856c --- /dev/null +++ b/frontend/src/pages/ttt/ttt.ts @@ -0,0 +1,137 @@ +import { addRoute, setTitle, type RouteHandlerReturn } from "@app/routing"; +import tttPage from "./ttt.html?raw"; +import { showError, showInfo, showSuccess } from "@app/toast"; + +type CellState = 'O' | 'X' | null + +class TTC { + private board: [ + CellState, CellState, CellState, + CellState, CellState, CellState, + CellState, CellState, CellState]; + private currentPlayer: 'O' | 'X'; + + constructor() { + this.board = [null,null,null,null,null,null,null,null,null]; + this.currentPlayer = 'X'; + } + + private changePlayer() + { + if (this.currentPlayer === 'X') + this.currentPlayer = 'O'; + else + this.currentPlayer = 'X'; + } + + private checkState(): 'winX' | 'winO' | 'draw' | 'ongoing' + { + const checkRow = (row: number): ('X' | 'O' | null) => { + if (this.board[row * 3] === null) + return null; + if (this.board[row * 3] === this.board[row * 3+1] && this.board[row + 1] === this.board[row * 3 + 2]) + return this.board[row * 3]; + return null; + } + + const checkCol = (col: number): ('X' | 'O' | null) => { + if (this.board[col] === null) return null; + + if (this.board[col] === this.board[col + 3] && this.board[col + 3] === this.board[col + 6]) + return this.board[col]; + return null; + } + + const checkDiag = (): ('X' | 'O' | null) => { + if (this.board[4] === null) return null + + if (this.board[0] === this.board[4] && this.board[4] === this.board[8]) + return this.board[4] + + if (this.board[2] === this.board[4] && this.board[4] === this.board[6]) + return this.board[4] + return null; + } + + + const row = (checkRow(0) ?? checkRow(1)) ?? checkRow(2); + const col = (checkCol(0) ?? checkCol(1)) ?? checkCol(2); + const diag = checkDiag(); + + if (row !== null) return `win${row}`; + if (col !== null) return `win${col}`; + if (diag !== null ) return `win${diag}`; + + if (this.board.filter(c => c === null).length === 0) + return 'draw'; + return 'ongoing'; + } + + public makeMove(idx: number): 'winX' | 'winO' | 'draw' | 'ongoing' | 'invalidMove' { + if (idx < 0 || idx >= this.board.length) + return 'invalidMove'; + if (this.board[idx] !== null) + return 'invalidMove'; + this.board[idx] = this.currentPlayer; + this.changePlayer(); + return this.checkState(); + } + + public getBoard(): [ + CellState, CellState, CellState, + CellState, CellState, CellState, + CellState, CellState, CellState] + { + return this.board; + } +} + + +async function handleTTT(): Promise +{ + let board = new TTC(); + + return { + html: tttPage, + postInsert: async (app) => { + let cells = app?.querySelectorAll(".ttt-grid-cell"); + + console.log(cells); + + cells?.forEach(function (c, idx) { + c.addEventListener('click', () => + { + const result = board.makeMove(idx); + switch(result) + { + case ('draw'): { + showInfo('Game is a draw'); + break; + } + case ('invalidMove'): { + showError('Move is invalid'); + break; + } + + case ('winX'): { + showSuccess('X won'); + break; + } + case ('winO'): { + showSuccess('O won'); + break; + } + } + + const board_state = board.getBoard(); + board_state.forEach( function (cell_state, cell_idx) { + cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; + }) + }) + }) + } + } +} + + +addRoute('/ttt', handleTTT) \ No newline at end of file From 9653b0cbe6324a2fd8680bf97ab9665a6d7eee4a Mon Sep 17 00:00:00 2001 From: Alessandro Petitcollin Date: Sun, 7 Dec 2025 01:14:53 +0100 Subject: [PATCH 10/66] (tic-tac-toe): Board is now locked once game is finished. --- frontend/src/pages/ttt/ttt.ts | 38 +++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts index e34856c..71b3a79 100644 --- a/frontend/src/pages/ttt/ttt.ts +++ b/frontend/src/pages/ttt/ttt.ts @@ -2,9 +2,15 @@ import { addRoute, setTitle, type RouteHandlerReturn } from "@app/routing"; import tttPage from "./ttt.html?raw"; import { showError, showInfo, showSuccess } from "@app/toast"; +// Represents the possible states of a cell on the board. +// `null` means that the cell is empty. type CellState = 'O' | 'X' | null +// Encapsulates the game logic. class TTC { + + private isGameOver: boolean; + private board: [ CellState, CellState, CellState, CellState, CellState, CellState, @@ -13,6 +19,7 @@ class TTC { constructor() { this.board = [null,null,null,null,null,null,null,null,null]; + this.isGameOver = false; this.currentPlayer = 'X'; } @@ -24,12 +31,13 @@ class TTC { this.currentPlayer = 'X'; } + // Analyzes the current board to determine if the game has ended. private checkState(): 'winX' | 'winO' | 'draw' | 'ongoing' { const checkRow = (row: number): ('X' | 'O' | null) => { if (this.board[row * 3] === null) return null; - if (this.board[row * 3] === this.board[row * 3+1] && this.board[row + 1] === this.board[row * 3 + 2]) + if (this.board[row * 3] === this.board[row * 3+1] && this.board[row * 3 + 1] === this.board[row * 3 + 2]) return this.board[row * 3]; return null; } @@ -67,14 +75,29 @@ class TTC { return 'ongoing'; } + // Attempts to place the current player's mark on the specified cell. + // @param idx - The index of the board (0-8) to place the mark. + // @returns The resulting game state, or `invalidMove` if the move is illegal. public makeMove(idx: number): 'winX' | 'winO' | 'draw' | 'ongoing' | 'invalidMove' { - if (idx < 0 || idx >= this.board.length) + if (this.isGameOver) { return 'invalidMove'; - if (this.board[idx] !== null) + } + if (idx < 0 || idx >= this.board.length) { return 'invalidMove'; + } + if (this.board[idx] !== null) { + return 'invalidMove'; + } this.board[idx] = this.currentPlayer; this.changePlayer(); - return this.checkState(); + + const result = this.checkState(); + + if (result !== 'ongoing') { + this.isGameOver = true; + } + + return result; } public getBoard(): [ @@ -86,9 +109,11 @@ class TTC { } } - +// Route handler for the Tic-Tac-Toe page. +// Instantiates the game logic and binds UI events. async function handleTTT(): Promise { + // Create a fresh instance for every page load. let board = new TTC(); return { @@ -122,7 +147,8 @@ async function handleTTT(): Promise break; } } - + + // Sync UI with Game State const board_state = board.getBoard(); board_state.forEach( function (cell_state, cell_idx) { cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; From fb1b69a0aeb738f2f85ee9dc23097ae35ea7e6bb Mon Sep 17 00:00:00 2001 From: Alessandro Petitcollin Date: Sun, 7 Dec 2025 01:31:29 +0100 Subject: [PATCH 11/66] (tic-tac-toe): Added button to restart game. --- frontend/src/pages/ttt/ttt.html | 5 ++++- frontend/src/pages/ttt/ttt.ts | 37 +++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/ttt/ttt.html b/frontend/src/pages/ttt/ttt.html index 48e7efa..7773eed 100644 --- a/frontend/src/pages/ttt/ttt.html +++ b/frontend/src/pages/ttt/ttt.html @@ -10,4 +10,7 @@
- \ No newline at end of file + + diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts index 71b3a79..801c2a1 100644 --- a/frontend/src/pages/ttt/ttt.ts +++ b/frontend/src/pages/ttt/ttt.ts @@ -75,6 +75,12 @@ class TTC { return 'ongoing'; } + public reset(): void { + this.board = [null,null,null,null,null,null,null,null,null]; + this.currentPlayer = 'X'; + this.isGameOver = false; + }; + // Attempts to place the current player's mark on the specified cell. // @param idx - The index of the board (0-8) to place the mark. // @returns The resulting game state, or `invalidMove` if the move is illegal. @@ -119,13 +125,24 @@ async function handleTTT(): Promise return { html: tttPage, postInsert: async (app) => { - let cells = app?.querySelectorAll(".ttt-grid-cell"); + if (!app) { + return; + } + + const cells = app.querySelectorAll(".ttt-grid-cell"); + const restartBtn = app.querySelector("#ttt-restart-btn"); + + const updateUI = () => { + const board_state = board.getBoard(); + board_state.forEach((cell_state, cell_idx) => { + cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; + }); + }; console.log(cells); cells?.forEach(function (c, idx) { - c.addEventListener('click', () => - { + c.addEventListener('click', () => { const result = board.makeMove(idx); switch(result) { @@ -140,6 +157,7 @@ async function handleTTT(): Promise case ('winX'): { showSuccess('X won'); + app?.querySelector('.ttt-grid')?.classList.add('pointer-events-none'); break; } case ('winO'): { @@ -152,9 +170,16 @@ async function handleTTT(): Promise const board_state = board.getBoard(); board_state.forEach( function (cell_state, cell_idx) { cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; - }) - }) - }) + }); + + updateUI(); + }); + }); + restartBtn?.addEventListener('click', () => { + board.reset(); + updateUI(); + showInfo('Game Restarted'); + }); } } } From cfdfbf04e19dde37523ebb08efebab308ff935a2 Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 11 Dec 2025 13:50:06 +0100 Subject: [PATCH 12/66] (misc) README.md updated --- frontend/src/pages/ttt/README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ttt/README.md b/frontend/src/pages/ttt/README.md index 73001a2..d8169ae 100644 --- a/frontend/src/pages/ttt/README.md +++ b/frontend/src/pages/ttt/README.md @@ -1,3 +1,29 @@ +Add another game with user history and matchmaking. +The goal of this major module, is to introduce a new game, distinct from Pong, and +incorporate features such as user history tracking and matchmaking. Key features +and objectives include: +◦ Develop a new, engaging game to diversify the platform’s offerings and enter- +tain users. +◦ Implement user history tracking to record and display individual users’ game- +play statistics. +◦ Create a matchmaking system to allow users to find opponents and participate +in fair and balanced matches. +◦ Ensure that user game history and matchmaking data are stored securely and +remain up-to-date. +◦ Optimize the performance and responsiveness of the new game to provide an +enjoyable user experience. Regularly update and maintain the game to fix +bugs, add new features, and enhance gameplay. +This major module aims to expand your platform by introducing a new game, +enhancing user engagement with gameplay history, and facilitating matchmaking +for an enjoyable gaming experience. + # TO-DO For now I am prohibited from working on the backend as per Maieul's request. -[ ] - Task for December 08 or 09: Tic-tac-toe should lock up once it's finished (i.e., draw or win) and print who won, how many turns and which row/col/diag the win happened in \ No newline at end of file +[ ] - Implement other game + [X] - (Done on Dec. 7 2025) Task for December 08 or 09: Tic-tac-toe should lock up once it's finished (i.e., draw or win) and print who won, how many turns and which row/col/diag the win happened in + +[ ] - Implement user history +[ ] - Implement matchmaking +[ ] - +[ ] - +[ ] - \ No newline at end of file From ea5f997fe064df00d77657d8d169c5038e7fff5b Mon Sep 17 00:00:00 2001 From: apetitco Date: Thu, 11 Dec 2025 13:53:48 +0100 Subject: [PATCH 13/66] (misc): entirely disabled backend --- src/tic-tac-toe/src/app.ts | 384 ++++++++++++++++++------------------- 1 file changed, 192 insertions(+), 192 deletions(-) diff --git a/src/tic-tac-toe/src/app.ts b/src/tic-tac-toe/src/app.ts index ca5fa57..f3aef85 100644 --- a/src/tic-tac-toe/src/app.ts +++ b/src/tic-tac-toe/src/app.ts @@ -1,198 +1,198 @@ -import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify'; -// TODO: Import Fastify formbody -// TODO: Import Fastify multipart -// TODO: Import shared database -// TODO: Import shared auth -// TODO: Import shared swagger -// TODO: Import shared utils -// TODO: Import socketio +// import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify'; +// // TODO: Import Fastify formbody +// // TODO: Import Fastify multipart +// // TODO: Import shared database +// // TODO: Import shared auth +// // TODO: Import shared swagger +// // TODO: Import shared utils +// // TODO: Import socketio -// @brief ??? -declare const __SERVICE_NAME: string; +// // @brief ??? +// declare const __SERVICE_NAME: string; -// TODO: Import the plugins defined for this microservice -// TODO: Import the routes defined for this microservice +// // TODO: Import the plugins defined for this microservice +// // TODO: Import the routes defined for this microservice -// @brief The microservice app (as a plugin for Fastify), kinda like a main function I guess ??? -// @param fastify -// @param opts -export const app: FastifyPluginAsync = async (fastify, opts): Promise => { - // Register all the fastify plugins that this app will use +// // @brief The microservice app (as a plugin for Fastify), kinda like a main function I guess ??? +// // @param fastify +// // @param opts +// export const app: FastifyPluginAsync = async (fastify, opts): Promise => { +// // Register all the fastify plugins that this app will use - // Once it is done: - fastify.ready((err) => { - if (err) { - throw err; - } - // TODO: Supposedly, something should be there I guess - }); -}; -// Export it as the default for this file. -export default app; - -// TODO: Understand what is this for in /src/chat/src/app.ts -// declare module 'fastify' { -// interface FastifyInstance { -// io: Server<{ -// hello: (message: string) => string; -// MsgObjectServer: (data: { message: ClientMessage }) => void; -// message: (msg: string) => void; -// testend: (sock_id_client: string) => void; -// }>; -// } -// } - -// TODO: Same for this, also in /src/chat/src/app.ts -// async function onReady(fastify: FastifyInstance) { -// function connectedUser(io?: Server, target?: string): number { -// let count = 0; -// const seen = new Set(); -// // <- only log/count unique usernames - -// for (const [socketId, username] of clientChat) { -// // Basic sanity checks -// if (typeof socketId !== 'string' || socketId.length === 0) { -// clientChat.delete(socketId); -// continue; -// } -// if (typeof username !== 'string' || username.length === 0) { -// clientChat.delete(socketId); -// continue; -// } - -// // If we have the io instance, attempt to validate the socket is still connected -// if (io && typeof io.sockets?.sockets?.get === 'function') { -// const s = io.sockets.sockets.get(socketId) as -// | Socket -// | undefined; -// // If socket not found or disconnected, remove from map and skip -// if (!s || s.disconnected) { -// clientChat.delete(socketId); -// continue; -// } - -// // Skip duplicates (DO NOT delete them — just don't count) -// if (seen.has(username)) { -// continue; -// } -// // socket exists and is connected -// seen.add(username); -// count++; -// // console.log(color.green,"count: ", count); -// console.log(color.yellow, 'Client:', color.reset, username); - -// const targetSocketId = target; -// io.to(targetSocketId!).emit('listObj', username); - -// console.log( -// color.yellow, -// 'Chat Socket ID:', -// color.reset, -// socketId, -// ); -// continue; -// } - -// // If no io provided, assume entries in the map are valid and count them. -// count++; -// console.log( -// color.red, -// 'Client (unverified):', -// color.reset, -// username, -// ); -// console.log( -// color.red, -// 'Chat Socket ID (unverified):', -// color.reset, -// socketId, -// ); +// // Once it is done: +// fastify.ready((err) => { +// if (err) { +// throw err; // } - -// return count; -// } - -// function broadcast(data: ClientMessage, sender?: string) { -// fastify.io.fetchSockets().then((sockets) => { -// for (const s of sockets) { -// if (s.id !== sender) { -// // Send REAL JSON object -// const clientName = clientChat.get(s.id) || null; -// if (clientName !== null) { -// s.emit('MsgObjectServer', { message: data }); -// } -// console.log(' Target window socket ID:', s.id); -// console.log(' Target window ID:', [...s.rooms]); -// console.log(' Sender window ID:', sender ? sender : 'none'); -// } -// } -// }); -// } - -// fastify.io.on('connection', (socket: Socket) => { -// socket.on('message', (message: string) => { -// console.info( -// color.blue, -// 'Socket connected!', -// color.reset, -// socket.id, -// ); -// console.log( -// color.blue, -// 'Received message from client', -// color.reset, -// message, -// ); - -// const obj: ClientMessage = JSON.parse(message) as ClientMessage; -// clientChat.set(socket.id, obj.user); -// console.log( -// color.green, -// 'Message from client', -// color.reset, -// `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, -// ); -// // Send object directly — DO NOT wrap it in a string -// broadcast(obj, obj.SenderWindowID); -// console.log( -// color.red, -// 'connected in the Chat :', -// connectedUser(fastify.io), -// color.reset, -// ); -// }); - -// socket.on('testend', (sock_id_cl: string) => { -// console.log('testend received from client socket id:', sock_id_cl); -// }); - -// socket.on('list', () => { -// console.log(color.red, 'list activated', color.reset, socket.id); -// connectedUser(fastify.io, socket.id); -// }); - -// socket.on('disconnecting', (reason) => { -// const clientName = clientChat.get(socket.id) || null; -// console.log( -// color.green, -// `Client disconnecting: ${clientName} (${socket.id}) reason:`, -// reason, -// ); -// if (reason === 'transport error') return; - -// if (clientName !== null) { -// const obj = { -// type: 'chat', -// user: clientName, -// token: '', -// text: 'LEFT the chat', -// timestamp: Date.now(), -// SenderWindowID: socket.id, -// }; - -// broadcast(obj, obj.SenderWindowID); -// // clientChat.delete(obj.user); -// } -// }); +// // TODO: Supposedly, something should be there I guess // }); -// } \ No newline at end of file +// }; +// // Export it as the default for this file. +// export default app; + +// // TODO: Understand what is this for in /src/chat/src/app.ts +// // declare module 'fastify' { +// // interface FastifyInstance { +// // io: Server<{ +// // hello: (message: string) => string; +// // MsgObjectServer: (data: { message: ClientMessage }) => void; +// // message: (msg: string) => void; +// // testend: (sock_id_client: string) => void; +// // }>; +// // } +// // } + +// // TODO: Same for this, also in /src/chat/src/app.ts +// // async function onReady(fastify: FastifyInstance) { +// // function connectedUser(io?: Server, target?: string): number { +// // let count = 0; +// // const seen = new Set(); +// // // <- only log/count unique usernames + +// // for (const [socketId, username] of clientChat) { +// // // Basic sanity checks +// // if (typeof socketId !== 'string' || socketId.length === 0) { +// // clientChat.delete(socketId); +// // continue; +// // } +// // if (typeof username !== 'string' || username.length === 0) { +// // clientChat.delete(socketId); +// // continue; +// // } + +// // // If we have the io instance, attempt to validate the socket is still connected +// // if (io && typeof io.sockets?.sockets?.get === 'function') { +// // const s = io.sockets.sockets.get(socketId) as +// // | Socket +// // | undefined; +// // // If socket not found or disconnected, remove from map and skip +// // if (!s || s.disconnected) { +// // clientChat.delete(socketId); +// // continue; +// // } + +// // // Skip duplicates (DO NOT delete them — just don't count) +// // if (seen.has(username)) { +// // continue; +// // } +// // // socket exists and is connected +// // seen.add(username); +// // count++; +// // // console.log(color.green,"count: ", count); +// // console.log(color.yellow, 'Client:', color.reset, username); + +// // const targetSocketId = target; +// // io.to(targetSocketId!).emit('listObj', username); + +// // console.log( +// // color.yellow, +// // 'Chat Socket ID:', +// // color.reset, +// // socketId, +// // ); +// // continue; +// // } + +// // // If no io provided, assume entries in the map are valid and count them. +// // count++; +// // console.log( +// // color.red, +// // 'Client (unverified):', +// // color.reset, +// // username, +// // ); +// // console.log( +// // color.red, +// // 'Chat Socket ID (unverified):', +// // color.reset, +// // socketId, +// // ); +// // } + +// // return count; +// // } + +// // function broadcast(data: ClientMessage, sender?: string) { +// // fastify.io.fetchSockets().then((sockets) => { +// // for (const s of sockets) { +// // if (s.id !== sender) { +// // // Send REAL JSON object +// // const clientName = clientChat.get(s.id) || null; +// // if (clientName !== null) { +// // s.emit('MsgObjectServer', { message: data }); +// // } +// // console.log(' Target window socket ID:', s.id); +// // console.log(' Target window ID:', [...s.rooms]); +// // console.log(' Sender window ID:', sender ? sender : 'none'); +// // } +// // } +// // }); +// // } + +// // fastify.io.on('connection', (socket: Socket) => { +// // socket.on('message', (message: string) => { +// // console.info( +// // color.blue, +// // 'Socket connected!', +// // color.reset, +// // socket.id, +// // ); +// // console.log( +// // color.blue, +// // 'Received message from client', +// // color.reset, +// // message, +// // ); + +// // const obj: ClientMessage = JSON.parse(message) as ClientMessage; +// // clientChat.set(socket.id, obj.user); +// // console.log( +// // color.green, +// // 'Message from client', +// // color.reset, +// // `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, +// // ); +// // // Send object directly — DO NOT wrap it in a string +// // broadcast(obj, obj.SenderWindowID); +// // console.log( +// // color.red, +// // 'connected in the Chat :', +// // connectedUser(fastify.io), +// // color.reset, +// // ); +// // }); + +// // socket.on('testend', (sock_id_cl: string) => { +// // console.log('testend received from client socket id:', sock_id_cl); +// // }); + +// // socket.on('list', () => { +// // console.log(color.red, 'list activated', color.reset, socket.id); +// // connectedUser(fastify.io, socket.id); +// // }); + +// // socket.on('disconnecting', (reason) => { +// // const clientName = clientChat.get(socket.id) || null; +// // console.log( +// // color.green, +// // `Client disconnecting: ${clientName} (${socket.id}) reason:`, +// // reason, +// // ); +// // if (reason === 'transport error') return; + +// // if (clientName !== null) { +// // const obj = { +// // type: 'chat', +// // user: clientName, +// // token: '', +// // text: 'LEFT the chat', +// // timestamp: Date.now(), +// // SenderWindowID: socket.id, +// // }; + +// // broadcast(obj, obj.SenderWindowID); +// // // clientChat.delete(obj.user); +// // } +// // }); +// // }); +// // } \ No newline at end of file From f2a5285479f145809f0ef79af1e95c00148f2693 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 25 Nov 2025 17:49:49 +0100 Subject: [PATCH 14/66] Added Date.now ClientChat that stores user, socket id and date --- frontend/src/pages/chat/chat.html | 32 +++++++++---------- frontend/src/pages/chat/chat.ts | 46 ++++++++++++++++++++++++++- frontend/src/routing/index.ts | 1 + src/@shared/package.json | 2 +- src/auth/package.json | 2 +- src/chat/src/app.ts | 52 ++++++++++++++++++++----------- src/pnpm-lock.yaml | 18 +++++------ src/user/package.json | 2 +- 8 files changed, 107 insertions(+), 48 deletions(-) diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 1e765fc..a0f0dde 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,19 +1,19 @@
- -

- Chat Box -


- - -

Welcome


-
-
-
- - -
-
-

From this Chat Box you can send messages to other players

+ +

+ Chat Box +


+ + +

Welcome


+
+
+
+ +
-
\ No newline at end of file +
+

From this Chat Box you can send messages to other players

+
+ \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 7c30261..18bca7a 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -12,6 +12,33 @@ document.addEventListener('ft:pageChange', () => { __socket = undefined; console.log("Page changed"); }) + + +document.addEventListener("visibilitychange", async () => { + + // When user leaves tab + if (document.visibilityState === "hidden") { + console.log("User LEFT this tab"); + + // if (__socket) { + // __socket.close(); + // __socket = undefined; + // } + + return; + } + + // When user returns to tab → soft reload using imported HTML file + if (document.visibilityState === "visible") { + // location.reload(); + //console.log(location.replace(location.href)); + + + console.log('Chat Visible') + } +}); + + function getSocket(): Socket { if (__socket === undefined) __socket = io("wss://localhost:8888", { @@ -23,6 +50,10 @@ function getSocket(): Socket { } +async function isLoggedIn() { + return getUser() || null; +} + function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -43,6 +74,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }; socket.emit('message', JSON.stringify(message)); }); + // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: any) => { console.log("Message Obj Recieved:", data.message); @@ -109,6 +141,11 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log(`Added new message: ${text}`) }; + + socket.on("welcome", (data) => { + addMessage(`${data.msg}`); + }); + // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -141,9 +178,13 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Help Text button bconnected?.addEventListener("click", async () => { + + const loggedIn = await isLoggedIn(); + + if (loggedIn?.name === undefined) return ; if (chatWindow) { addMessage('@list - lists all connected users in the chat'); - await socket.emit('list'); + socket.emit('list'); } }); socket.on('listObj', (list: string) => { @@ -152,6 +193,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); + + + // Enter key to send message sendtextbox!.addEventListener('keydown', (event) => { if (event.key === 'Enter') { diff --git a/frontend/src/routing/index.ts b/frontend/src/routing/index.ts index 0c0b9a4..9e504db 100644 --- a/frontend/src/routing/index.ts +++ b/frontend/src/routing/index.ts @@ -196,6 +196,7 @@ export async function handleRoute() { return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`) const app = document.getElementById('app')!; document.dispatchEvent(new CustomEvent('ft:pageChange' as any, {} as any) as any); + document.dispatchEvent(new CustomEvent('ft:tabChange' as any, {} as any) as any); let ret = await executeRouteHandler(route_handler, window.location.pathname, args) app.innerHTML = ret.html; if (ret.postInsert) { diff --git a/src/@shared/package.json b/src/@shared/package.json index edcf6a8..dad8675 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -20,7 +20,7 @@ "fastify-plugin": "^5.1.0", "joi": "^18.0.2", "otp": "^1.1.2", - "typebox": "^1.0.55", + "typebox": "^1.0.56", "uuidv7": "^1.0.2" }, "devDependencies": { diff --git a/src/auth/package.json b/src/auth/package.json index 855e7f8..adb5e2e 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.55" + "typebox": "^1.0.56" }, "devDependencies": { "@types/node": "^22.19.1", diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 4749bb9..0dbac4d 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -11,7 +11,12 @@ declare const __SERVICE_NAME: string; // Global map of clients // key = socket, value = clientname -const clientChat = new Map(); +interface ClientInfo { + user: string; + lastSeen: number; +} + +const clientChat = new Map(); // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); @@ -79,13 +84,13 @@ async function onReady(fastify: FastifyInstance) { const seen = new Set(); // <- only log/count unique usernames - for (const [socketId, username] of clientChat) { + for (const [socketId, username,] of clientChat) { // Basic sanity checks if (typeof socketId !== 'string' || socketId.length === 0) { clientChat.delete(socketId); continue; } - if (typeof username !== 'string' || username.length === 0) { + if (typeof username.user !== 'string' || username.user.length === 0) { clientChat.delete(socketId); continue; } @@ -102,17 +107,17 @@ async function onReady(fastify: FastifyInstance) { } // Skip duplicates (DO NOT delete them — just don't count) - if (seen.has(username)) { + if (seen.has(username.user)) { continue; } // socket exists and is connected - seen.add(username); + seen.add(username.user); count++; // console.log(color.green,"count: ", count); - console.log(color.yellow, 'Client:', color.reset, username); + console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; - io.to(targetSocketId!).emit('listObj', username); + io.to(targetSocketId!).emit('listObj', username.user); console.log( color.yellow, @@ -147,8 +152,8 @@ async function onReady(fastify: FastifyInstance) { for (const s of sockets) { if (s.id !== sender) { // Send REAL JSON object - const clientName = clientChat.get(s.id) || null; - if (clientName !== null) { + const clientName = clientChat.get(s.id)?.user; + if (clientName !== undefined) { s.emit('MsgObjectServer', { message: data }); } console.log(' Target window socket ID:', s.id); @@ -159,7 +164,11 @@ async function onReady(fastify: FastifyInstance) { }); } + fastify.io.on('connection', (socket: Socket) => { + + + socket.on('message', (message: string) => { console.info( color.blue, @@ -173,9 +182,9 @@ async function onReady(fastify: FastifyInstance) { color.reset, message, ); - + const obj: ClientMessage = JSON.parse(message) as ClientMessage; - clientChat.set(socket.id, obj.user); + clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); console.log( color.green, 'Message from client', @@ -191,35 +200,40 @@ async function onReady(fastify: FastifyInstance) { color.reset, ); }); - + + socket.emit("welcome", { + msg: `Welcome to the chat!`, + id: socket.id + }); + socket.on('testend', (sock_id_cl: string) => { console.log('testend received from client socket id:', sock_id_cl); }); - + socket.on('list', () => { console.log(color.red, 'list activated', color.reset, socket.id); connectedUser(fastify.io, socket.id); }); - + socket.on('disconnecting', (reason) => { const clientName = clientChat.get(socket.id) || null; console.log( color.green, - `Client disconnecting: ${clientName} (${socket.id}) reason:`, + `Client disconnecting: ${clientName?.user} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - - if (clientName !== null) { + + if (clientName?.user !== null) { const obj = { type: 'chat', - user: clientName, + user: clientName!.user, token: '', text: 'LEFT the chat', timestamp: Date.now(), SenderWindowID: socket.id, }; - + broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index e77156a..bc84ef1 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -88,8 +88,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 uuidv7: specifier: ^1.0.2 version: 1.0.2 @@ -131,8 +131,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 devDependencies: '@types/node': specifier: ^22.19.1 @@ -266,8 +266,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 devDependencies: '@types/node': specifier: ^22.19.1 @@ -3025,8 +3025,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typebox@1.0.55: - resolution: {integrity: sha512-TP02wN0B6tDZngprrGVu/Z9s/QUyVEmR7VIg1yEOtsqyDdXXEoQPSfWdkD2PsA2lGLxu6GgwOTtGZVS9CAoERg==} + typebox@1.0.56: + resolution: {integrity: sha512-KMd1DJnIRqLUzAicpFmGqgmt+/IePCEmT/Jtywyyyn0hK6+dupQnxm7OAIn/cL/vu22jKi1XvDjDhrpatZ46kA==} typescript-eslint@8.48.0: resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} @@ -6156,7 +6156,7 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typebox@1.0.55: {} + typebox@1.0.56: {} typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): dependencies: diff --git a/src/user/package.json b/src/user/package.json index 4f197a3..9efe221 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -26,7 +26,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.55" + "typebox": "^1.0.56" }, "devDependencies": { "@types/node": "^22.19.1", From 4d838585b4e5ad6ed7812c77890990c79509d60a Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 14:41:21 +0100 Subject: [PATCH 15/66] added tab control to chat, When user leaves tab open on chat --- .../api/generated/.openapi-generator/FILES | 1 - .../src/api/generated/apis/OpenapiOtherApi.ts | 13 +-- frontend/src/api/generated/models/index.ts | 1 - frontend/src/pages/chat/chat.ts | 60 ++++++++---- src/chat/openapi.json | 26 ----- src/chat/src/app.ts | 95 ++++++++++++++++--- src/openapi.json | 26 ----- src/package.json | 2 +- src/pnpm-lock.yaml | 46 ++++----- 9 files changed, 151 insertions(+), 119 deletions(-) diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index b02736b..af12e66 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -2,7 +2,6 @@ apis/OpenapiOtherApi.ts apis/index.ts index.ts models/ChatTest200Response.ts -models/ChatTest500Response.ts models/DisableOtp200Response.ts models/DisableOtp401Response.ts models/DisableOtp500Response.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index f03d19e..fda4f60 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -16,7 +16,6 @@ import * as runtime from '../runtime'; import type { ChatTest200Response, - ChatTest500Response, DisableOtp200Response, DisableOtp401Response, DisableOtp500Response, @@ -50,8 +49,6 @@ import type { import { ChatTest200ResponseFromJSON, ChatTest200ResponseToJSON, - ChatTest500ResponseFromJSON, - ChatTest500ResponseToJSON, DisableOtp200ResponseFromJSON, DisableOtp200ResponseToJSON, DisableOtp401ResponseFromJSON, @@ -135,7 +132,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -162,19 +159,15 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // Object response for status 401 return new runtime.JSONApiResponse(response, (jsonValue) => StatusOtp401ResponseFromJSON(jsonValue)); } - if (response.status === 500) { - // Object response for status 500 - return new runtime.JSONApiResponse(response, (jsonValue) => ChatTest500ResponseFromJSON(jsonValue)); - } // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes // Only throw if responses were defined but none matched the actual status code - throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 500`); + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401`); } /** */ - async chatTest(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async chatTest(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.chatTestRaw(initOverrides); return await response.value(); } diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index b4d967b..a4059b0 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -1,7 +1,6 @@ /* tslint:disable */ /* eslint-disable */ export * from './ChatTest200Response'; -export * from './ChatTest500Response'; export * from './DisableOtp200Response'; export * from './DisableOtp401Response'; export * from './DisableOtp500Response'; diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 18bca7a..1e0d53f 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -16,29 +16,49 @@ document.addEventListener('ft:pageChange', () => { document.addEventListener("visibilitychange", async () => { - // When user leaves tab + const socketId = __socket || undefined; + let oldName = localStorage.getItem("oldName") || undefined; + if (socketId == undefined) return; if (document.visibilityState === "hidden") { - console.log("User LEFT this tab"); - - // if (__socket) { - // __socket.close(); - // __socket = undefined; - // } - + let userName = await updateUser(); + oldName = userName?.name || "undefined"; + localStorage.setItem("oldName", oldName); + socketId.emit("client_left"); return; } - - // When user returns to tab → soft reload using imported HTML file if (document.visibilityState === "visible") { - // location.reload(); - //console.log(location.replace(location.href)); - - - console.log('Chat Visible') + const res = await client.guestLogin(); + let user = await updateUser(); + socketId.emit('client_entered', { + userName: oldName, + user: user?.name, + }); + setTitle('Chat Page'); + return; } }); + +async function getUserName(): Promise { + try { + const res = await client.guestLogin(); + + if (res.kind !== "success") { + console.error("Login failed:", res.msg); + return null; + } + + const user = await updateUser(); + if (!user) return null; + + return user.name; // <-- return the username + } catch (err) { + console.error("getUserName error:", err); + return null; + } +} + function getSocket(): Socket { if (__socket === undefined) __socket = io("wss://localhost:8888", { @@ -142,8 +162,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }; - socket.on("welcome", (data) => { - addMessage(`${data.msg}`); + socket.once('welcome', (data) => { + addMessage (`${data.msg} ` + getUser()?.name); }); // Send button @@ -161,7 +181,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn timestamp: Date.now(), SenderWindowID: socket.id, }; - socket.send(JSON.stringify(message)); + socket.emit('message', JSON.stringify(message)); } sendtextbox.value = ""; } @@ -182,6 +202,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const loggedIn = await isLoggedIn(); if (loggedIn?.name === undefined) return ; + const res = await client.guestLogin(); + let user = await updateUser(); + console.log('USER ', user?.name); if (chatWindow) { addMessage('@list - lists all connected users in the chat'); socket.emit('list'); @@ -228,3 +251,4 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }; addRoute('/chat', handleChat, { bypass_auth: true }); +addRoute('/chat/', handleChat, { bypass_auth: true }); diff --git a/src/chat/openapi.json b/src/chat/openapi.json index 8afe3d2..bcebf94 100644 --- a/src/chat/openapi.json +++ b/src/chat/openapi.json @@ -112,32 +112,6 @@ } } } - }, - "500": { - "description": "Default Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "msg" - ], - "properties": { - "kind": { - "enum": [ - "failed" - ] - }, - "msg": { - "enum": [ - "chat.failed.generic" - ] - } - } - } - } - } } } } diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 0dbac4d..abc7a53 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -163,12 +163,10 @@ async function onReady(fastify: FastifyInstance) { } }); } - + fastify.io.on('connection', (socket: Socket) => { - - - + socket.on('message', (message: string) => { console.info( color.blue, @@ -191,6 +189,10 @@ async function onReady(fastify: FastifyInstance) { color.reset, `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, ); + socket.emit('welcome', { + msg: `Welcome to the chat! `, + }); + // Send object directly — DO NOT wrap it in a string broadcast(obj, obj.SenderWindowID); console.log( @@ -201,33 +203,35 @@ async function onReady(fastify: FastifyInstance) { ); }); - socket.emit("welcome", { - msg: `Welcome to the chat!`, - id: socket.id - }); - socket.on('testend', (sock_id_cl: string) => { console.log('testend received from client socket id:', sock_id_cl); }); + socket.on('wakeup', (message: string) => { + const obj: ClientMessage = JSON.parse(message) as ClientMessage; + clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); + connectedUser(fastify.io), + console.log('Wakeup: ', message); + }); + socket.on('list', () => { console.log(color.red, 'list activated', color.reset, socket.id); connectedUser(fastify.io, socket.id); }); socket.on('disconnecting', (reason) => { - const clientName = clientChat.get(socket.id) || null; + const clientName = clientChat.get(socket.id)?.user|| null; console.log( color.green, - `Client disconnecting: ${clientName?.user} (${socket.id}) reason:`, + `Client disconnecting: ${clientName} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - if (clientName?.user !== null) { + if (clientName !== null) { const obj = { type: 'chat', - user: clientName!.user, + user: clientName, token: '', text: 'LEFT the chat', timestamp: Date.now(), @@ -238,5 +242,70 @@ async function onReady(fastify: FastifyInstance) { // clientChat.delete(obj.user); } }); + + socket.on('client_left', (reason) => { + const clientName = clientChat.get(socket.id)?.user|| null; + console.log( + color.green, + `Client left the Chat: ${clientName} (${socket.id}) reason:`, + reason, + ); + if (reason === 'transport error') return; + + if (clientName !== null) { + const obj = { + type: 'chat', + user: clientName, + token: '', + text: 'LEFT the chat but the window is still open', + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(obj.SenderWindowID); + broadcast(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + + + socket.on('client_entered', (data) => { + + // data may be undefined (when frontend calls emit with no payload) + const userNameFromFrontend = data?.userName || null; + const userFromFrontend = data?.user || null; + let clientName = clientChat.get(socket.id)?.user || null; + const client = clientChat.get(socket.id) || null; + let text = 'is back in the chat'; + + // connectedUser(fastify.io, socket.id); + if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; + if(client === null) {console.log('ERROR: client is NULL'); return;}; + + if (userNameFromFrontend !== userFromFrontend) { + text = `'is back in the chat, I used to be called '${userNameFromFrontend}`; + clientName = userFromFrontend; + if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; + if (client) { + client.user = clientName; + } + } + console.log( + color.green, + `Client entered the Chat: ${clientName} (${socket.id})` + ); + if (clientName !== null) { + const obj = { + type: 'chat', + user: clientName, // server-side stored name + frontendUserName: userNameFromFrontend, // from frontend + frontendUser: userFromFrontend, // from frontend + token: '', + text: text, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + broadcast(obj, obj.SenderWindowID); + } + }); }); } diff --git a/src/openapi.json b/src/openapi.json index f370df1..4327973 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -1294,32 +1294,6 @@ } } } - }, - "500": { - "description": "Default Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "msg" - ], - "properties": { - "kind": { - "enum": [ - "failed" - ] - }, - "msg": { - "enum": [ - "chat.failed.generic" - ] - } - } - } - } - } } }, "tags": [ diff --git a/src/package.json b/src/package.json index 22edb07..c833a12 100644 --- a/src/package.json +++ b/src/package.json @@ -36,7 +36,7 @@ "vite": "^7.2.4" }, "dependencies": { - "@redocly/cli": "^2.11.1", + "@redocly/cli": "^2.12.0", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index bc84ef1..c152612 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.11.1 - version: 2.11.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.0 + version: 2.12.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -946,27 +946,27 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.11.1': - resolution: {integrity: sha512-doNs+sdrFzzXmyf1yIeJbPh8OChacHWkvTE9N0QbuCmnYQ4k0v1IMP20qsitkwR+fK8O1hXSnFnSTVvIunMVVw==} + '@redocly/cli@2.12.0': + resolution: {integrity: sha512-/q8RnBe+Duo+XYFCG8LnaD0kroGZ8MoS6575Xq59tCgjaCL16F+pZZ75xNBU2oXfEypJClNz/6ilc2G0q1+tlw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.38.0': - resolution: {integrity: sha512-kSgMG3rRzgXIP/6gWMRuWbu9/ms0Cyuphcx19dPR9qlgc1tt9IKYPsFQ+KhJuEtqd3bcY/+Uflysf33dQkZWVQ==} + '@redocly/config@0.40.0': + resolution: {integrity: sha512-MZQZs7QEGnue3rVN9Q9QvDbcGjesxbpKXUvDeckS69R1xjtgsnT9B39VA25zmwSJtgUeA9ST+sMf9GxIqixNbw==} '@redocly/openapi-core@1.34.5': resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.11.1': - resolution: {integrity: sha512-FVCDnZxaoUJwLQxfW4inCojxUO56J3ntu7dDAE2qyWd6tJBK45CnXMQQUxpqeRTeXROr3jYQoApAw+GCEnyBeg==} + '@redocly/openapi-core@2.12.0': + resolution: {integrity: sha512-RsVwmRD0KhyJbR8acIeU98ce6N+/YCuLJf6IGN+2SOsbwnDhnI5MG0TFV9D7URK/ukEewaNA701dVYsoP1VtRQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.11.1': - resolution: {integrity: sha512-jSMJvCJeo5gmhQfg82AhuwCG0h8gbW5vqHyRITBu8KHVsBiQTgvfhXepu8SKHeJu0OexYtEc0nUnGLJlefevYw==} + '@redocly/respect-core@2.12.0': + resolution: {integrity: sha512-mrYrfE81shSRS96ygXaRiSithV4Fe4Y7XlSYLSTfM8Lo3YAz7Geirg7HZ5fNFsI+hdW05ZuQewqpKL8XLwaAeA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -1483,8 +1483,8 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.0: + resolution: {integrity: sha512-vXiThu1/rlos7EGu8TuNZQEg2e9TvhH9dmS4T4ZVzB7Ao1agEZ6EG3sn5n+hZRYUgduISd1HpngFzAZiDGm5vQ==} engines: {node: '>=18'} core-js@3.47.0: @@ -3436,7 +3436,7 @@ snapshots: '@fastify/cookie@11.0.2': dependencies: - cookie: 1.0.2 + cookie: 1.1.0 fastify-plugin: 5.1.0 '@fastify/deepmerge@3.1.0': {} @@ -3873,14 +3873,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.11.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.11.1(ajv@8.17.1) - '@redocly/respect-core': 2.11.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.0(ajv@8.17.1) + '@redocly/respect-core': 2.12.0(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3913,7 +3913,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.38.0': + '@redocly/config@0.40.0': dependencies: json-schema-to-ts: 2.7.2 @@ -3931,10 +3931,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.11.1(ajv@8.17.1)': + '@redocly/openapi-core@2.12.0(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 - '@redocly/config': 0.38.0 + '@redocly/config': 0.40.0 ajv-formats: 2.1.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 @@ -3945,12 +3945,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.11.1(ajv@8.17.1)': + '@redocly/respect-core@2.12.0(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.11.4 - '@redocly/openapi-core': 2.11.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.0(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -4448,7 +4448,7 @@ snapshots: cookie@0.7.2: {} - cookie@1.0.2: {} + cookie@1.1.0: {} core-js@3.47.0: {} @@ -5167,7 +5167,7 @@ snapshots: light-my-request@6.6.0: dependencies: - cookie: 1.0.2 + cookie: 1.1.0 process-warning: 4.0.1 set-cookie-parser: 2.7.2 From 18956ee9534c255a9cac0d059b120bb3ae443428 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 19:58:34 +0100 Subject: [PATCH 16/66] Added tab 'hidden' detection when chat is on, added css red text messages sent --- frontend/src/chat/chat.css | 11 ++-- frontend/src/pages/chat/chat.ts | 84 +++++++++++++------------ src/chat/src/app.ts | 107 ++++++++++++++++++++------------ 3 files changed, 115 insertions(+), 87 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index d02bc50..91901a4 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -80,20 +80,17 @@ .mainboxDisplay button { @apply - cursor-pointer; + cursor-pointer } p { @apply text-black - - - } -.div-test { +div-test { @apply - italic - + text-red-800 + text-right } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 1e0d53f..9399b0c 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -14,6 +14,28 @@ document.addEventListener('ft:pageChange', () => { }) +function getSocket(): Socket { + if (__socket === undefined) + __socket = io("wss://localhost:8888", { + path: "/api/chat/socket.io/", + secure: false, + transports: ["websocket"], + }); + return __socket; +} + + +async function isLoggedIn() { + return getUser() || null; +} + + + +function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { + let socket = getSocket(); + + + document.addEventListener("visibilitychange", async () => { const socketId = __socket || undefined; @@ -40,44 +62,6 @@ document.addEventListener("visibilitychange", async () => { -async function getUserName(): Promise { - try { - const res = await client.guestLogin(); - - if (res.kind !== "success") { - console.error("Login failed:", res.msg); - return null; - } - - const user = await updateUser(); - if (!user) return null; - - return user.name; // <-- return the username - } catch (err) { - console.error("getUserName error:", err); - return null; - } -} - -function getSocket(): Socket { - if (__socket === undefined) - __socket = io("wss://localhost:8888", { - path: "/api/chat/socket.io/", - secure: false, - transports: ["websocket"], - }); - return __socket; -} - - -async function isLoggedIn() { - return getUser() || null; -} - - - -function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { - let socket = getSocket(); // Listen for the 'connect' event socket.on("connect", () => { console.log("I AM Connected to the server:", socket.id); @@ -154,7 +138,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const addMessage = (text: string) => { if (!chatWindow) return; - const messageElement = document.createElement("div"); + const messageElement = document.createElement("div-test"); messageElement.textContent = text; chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; @@ -200,14 +184,20 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn bconnected?.addEventListener("click", async () => { const loggedIn = await isLoggedIn(); - + let oldUser = localStorage.getItem("oldName") || undefined; if (loggedIn?.name === undefined) return ; + oldUser = loggedIn.name || "undefined"; const res = await client.guestLogin(); let user = await updateUser(); + localStorage.setItem("oldName", oldUser); + console.log('USER ', user?.name); if (chatWindow) { addMessage('@list - lists all connected users in the chat'); - socket.emit('list'); + socket.emit('list', { + oldUser: oldUser, + user: user?.name + }); } }); socket.on('listObj', (list: string) => { @@ -229,10 +219,22 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Whoami button to display user name bwhoami?.addEventListener('click', async () => { try { + const loggedIn = await isLoggedIn(); + let oldUser = localStorage.getItem("oldName") || undefined; + oldUser = loggedIn?.name || "undefined"; + localStorage.setItem("oldName", oldUser); + const res = await client.guestLogin(); switch (res.kind) { case 'success': { let user = await updateUser(); + console.log('USER ', user?.name); + if (chatWindow) { + socket.emit('updateClientName', { + oldUser: oldUser, + user: user?.name + }); + } if (user === null) return showError('Failed to get user: no user ?'); setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`); diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index abc7a53..7a92dc5 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -74,6 +74,9 @@ declare module 'fastify' { MsgObjectServer: (data: { message: ClientMessage }) => void; message: (msg: string) => void; testend: (sock_id_client: string) => void; + client_entered: (userName: string, user: string) => void; + list: (oldUser: string, user: string) => void; + updateClientName: (oldUser: string, user: string) => void; }>; } } @@ -84,7 +87,7 @@ async function onReady(fastify: FastifyInstance) { const seen = new Set(); // <- only log/count unique usernames - for (const [socketId, username,] of clientChat) { + for (const [socketId, username] of clientChat) { // Basic sanity checks if (typeof socketId !== 'string' || socketId.length === 0) { clientChat.delete(socketId); @@ -163,10 +166,9 @@ async function onReady(fastify: FastifyInstance) { } }); } - - + fastify.io.on('connection', (socket: Socket) => { - + socket.on('message', (message: string) => { console.info( color.blue, @@ -180,19 +182,19 @@ async function onReady(fastify: FastifyInstance) { color.reset, message, ); - + const obj: ClientMessage = JSON.parse(message) as ClientMessage; clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); console.log( color.green, 'Message from client', color.reset, - `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, + `Sender: login name: ${obj.user} - windowID ${obj.SenderWindowID} - text message: ${obj.text}`, ); socket.emit('welcome', { - msg: `Welcome to the chat! `, + msg: 'Welcome to the chat!', }); - + // Send object directly — DO NOT wrap it in a string broadcast(obj, obj.SenderWindowID); console.log( @@ -202,32 +204,57 @@ async function onReady(fastify: FastifyInstance) { color.reset, ); }); - + socket.on('testend', (sock_id_cl: string) => { console.log('testend received from client socket id:', sock_id_cl); }); - - socket.on('wakeup', (message: string) => { - const obj: ClientMessage = JSON.parse(message) as ClientMessage; - clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); - connectedUser(fastify.io), - console.log('Wakeup: ', message); - }); - socket.on('list', () => { - console.log(color.red, 'list activated', color.reset, socket.id); + socket.on('list', (object) => { + + const userFromFrontend = object || null; + const client = clientChat.get(socket.id) || null; + + console.log(color.red, 'list activated', userFromFrontend, color.reset, socket.id); + + if (userFromFrontend.oldUser !== userFromFrontend.user) { + console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset); + if (client === null) { + console.log('ERROR: clientName is NULL'); + return; + }; + if (client) { + client.user = userFromFrontend.user; + } + } connectedUser(fastify.io, socket.id); }); - + + socket.on('updateClientName', (object) => { + const userFromFrontend = object || null; + const client = clientChat.get(socket.id) || null; + console.log(color.red, 'whoAMi activated', userFromFrontend, color.reset, socket.id); + if (userFromFrontend.oldUser !== userFromFrontend.user) { + console.log(color.red, 'whoAMi activated', userFromFrontend.oldUser, color.reset); + if (client === null) { + console.log('ERROR: clientName is NULL'); + return; + }; + if (client) { + client.user = userFromFrontend.user; + } + } + }); + + socket.on('disconnecting', (reason) => { - const clientName = clientChat.get(socket.id)?.user|| null; + const clientName = clientChat.get(socket.id)?.user || null; console.log( color.green, `Client disconnecting: ${clientName} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - + if (clientName !== null) { const obj = { type: 'chat', @@ -237,21 +264,20 @@ async function onReady(fastify: FastifyInstance) { timestamp: Date.now(), SenderWindowID: socket.id, }; - + broadcast(obj, obj.SenderWindowID); - // clientChat.delete(obj.user); } }); socket.on('client_left', (reason) => { - const clientName = clientChat.get(socket.id)?.user|| null; + const clientName = clientChat.get(socket.id)?.user || null; console.log( color.green, `Client left the Chat: ${clientName} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - + if (clientName !== null) { const obj = { type: 'chat', @@ -261,13 +287,12 @@ async function onReady(fastify: FastifyInstance) { timestamp: Date.now(), SenderWindowID: socket.id, }; - console.log(obj.SenderWindowID); + console.log(obj.SenderWindowID); broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } }); - socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload) @@ -276,29 +301,33 @@ async function onReady(fastify: FastifyInstance) { let clientName = clientChat.get(socket.id)?.user || null; const client = clientChat.get(socket.id) || null; let text = 'is back in the chat'; - - // connectedUser(fastify.io, socket.id); - if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; - if(client === null) {console.log('ERROR: client is NULL'); return;}; - + + if (clientName === null) { + console.log('ERROR: clientName is NULL'); return; + }; + if (client === null) { + console.log('ERROR: client is NULL'); return; + }; if (userNameFromFrontend !== userFromFrontend) { text = `'is back in the chat, I used to be called '${userNameFromFrontend}`; clientName = userFromFrontend; - if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; + if (clientName === null) { + console.log('ERROR: clientName is NULL'); return; + }; if (client) { - client.user = clientName; - } + client.user = clientName; + } } console.log( color.green, - `Client entered the Chat: ${clientName} (${socket.id})` + `Client entered the Chat: ${clientName} (${socket.id})`, ); if (clientName !== null) { const obj = { type: 'chat', - user: clientName, // server-side stored name - frontendUserName: userNameFromFrontend, // from frontend - frontendUser: userFromFrontend, // from frontend + user: clientName, + frontendUserName: userNameFromFrontend, + frontendUser: userFromFrontend, token: '', text: text, timestamp: Date.now(), From d9d3ada6a0b6f38ff18305ac2bcc917f374f25e4 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 20:43:08 +0100 Subject: [PATCH 17/66] minor stuff done like BG color of chat window --- frontend/src/chat/chat.css | 3 ++- frontend/src/pages/chat/chat.html | 3 +-- frontend/src/pages/chat/chat.ts | 10 +++++++--- src/chat/src/app.ts | 11 ++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 91901a4..e2ddc60 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -62,7 +62,8 @@ flex items-center justify-center - bg-gray-100; + bg-[#43536b]; + } .mainboxDisplay { diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index a0f0dde..21005f1 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -5,8 +5,7 @@ Chat Box
- -

Welcome


+

diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 9399b0c..ba8dea1 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -43,9 +43,13 @@ document.addEventListener("visibilitychange", async () => { if (socketId == undefined) return; if (document.visibilityState === "hidden") { let userName = await updateUser(); - oldName = userName?.name || "undefined"; - localStorage.setItem("oldName", oldName); - socketId.emit("client_left"); + oldName = userName?.name || undefined; + if (oldName === undefined) return; + localStorage.setItem('oldName', oldName); + socketId.emit('client_left', { + user: userName?.name, + why: 'tab window hidden - socket not dead', + }); return; } if (document.visibilityState === "visible") { diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 7a92dc5..83876fa 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -75,6 +75,7 @@ declare module 'fastify' { message: (msg: string) => void; testend: (sock_id_client: string) => void; client_entered: (userName: string, user: string) => void; + client_left: (userName: string, why: string) => void; list: (oldUser: string, user: string) => void; updateClientName: (oldUser: string, user: string) => void; }>; @@ -192,7 +193,7 @@ async function onReady(fastify: FastifyInstance) { `Sender: login name: ${obj.user} - windowID ${obj.SenderWindowID} - text message: ${obj.text}`, ); socket.emit('welcome', { - msg: 'Welcome to the chat!', + msg: 'Welcome to the chat! : ', }); // Send object directly — DO NOT wrap it in a string @@ -269,14 +270,14 @@ async function onReady(fastify: FastifyInstance) { } }); - socket.on('client_left', (reason) => { + socket.on('client_left', (data) => { const clientName = clientChat.get(socket.id)?.user || null; + const leftChat = data || null; console.log( color.green, - `Client left the Chat: ${clientName} (${socket.id}) reason:`, - reason, + `Left the Chat User: ${clientName} id Socket: ${socket.id} reason:`, + leftChat.why, ); - if (reason === 'transport error') return; if (clientName !== null) { const obj = { From 0ae24015a99566bddce19f467b07a5a513049d66 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 20:44:28 +0100 Subject: [PATCH 18/66] minor stuff done like BG color of chat window --- frontend/src/pages/chat/chat.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 21005f1..45596b5 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,11 +1,11 @@
- +

Chat Box


- - + +

From 61b8919995fabd2d3863f402b239094db35480d6 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 21:09:47 +0100 Subject: [PATCH 19/66] rebase with master --- src/pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index c152612..bcdb1ae 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -1483,8 +1483,8 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.1.0: - resolution: {integrity: sha512-vXiThu1/rlos7EGu8TuNZQEg2e9TvhH9dmS4T4ZVzB7Ao1agEZ6EG3sn5n+hZRYUgduISd1HpngFzAZiDGm5vQ==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} core-js@3.47.0: @@ -3436,7 +3436,7 @@ snapshots: '@fastify/cookie@11.0.2': dependencies: - cookie: 1.1.0 + cookie: 1.1.1 fastify-plugin: 5.1.0 '@fastify/deepmerge@3.1.0': {} @@ -4448,7 +4448,7 @@ snapshots: cookie@0.7.2: {} - cookie@1.1.0: {} + cookie@1.1.1: {} core-js@3.47.0: {} @@ -5167,7 +5167,7 @@ snapshots: light-my-request@6.6.0: dependencies: - cookie: 1.1.0 + cookie: 1.1.1 process-warning: 4.0.1 set-cookie-parser: 2.7.2 From f904a6b6aeff0af30039e7dfb412f0ee19a16ffe Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 27 Nov 2025 18:20:19 +0100 Subject: [PATCH 20/66] Added machine Name to display address of connect in logs --- docker-compose.yml | 2 +- frontend/src/chat/chat.css | 9 +- frontend/src/pages/chat/chat.ts | 156 +++++++++++++++++++++++++------- src/chat/src/app.ts | 23 +++-- 4 files changed, 147 insertions(+), 43 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 161cbb8..45f7af3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -141,7 +141,7 @@ services: - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA - DATABASE_DIR=/volumes/database - PROVIDER_FILE=/extra/providers.toml - + - SESSION_MANAGER=${SESSION_MANAGER} ############### # USER # diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index e2ddc60..b3cb9c3 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -92,6 +92,13 @@ p { div-test { @apply text-red-800 - text-right + text-right; + +} +div-notlog { + @apply + text-red-800 + text-3xl + text-center; } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index ba8dea1..65b5f69 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -5,6 +5,21 @@ import client from '@app/api' import { getUser, updateUser } from "@app/auth"; import io, { Socket } from 'socket.io-client'; +const color = { + red: 'color: red; font-weight: bold;', + green: 'color: green; font-weight: bold;', + yellow: 'color: orange; font-weight: bold;', + blue: 'color: blue; font-weight: bold;', + reset: '', // not needed in browser +}; + + +// get the name of the machine useed to connect +const machineHostName = window.location.hostname; +console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); + + + let __socket: Socket | undefined = undefined; document.addEventListener('ft:pageChange', () => { if (__socket !== undefined) @@ -15,8 +30,10 @@ document.addEventListener('ft:pageChange', () => { function getSocket(): Socket { + let addressHost = `wss://${machineHostName}:8888`; if (__socket === undefined) - __socket = io("wss://localhost:8888", { + + __socket = io(addressHost, { path: "/api/chat/socket.io/", secure: false, transports: ["websocket"], @@ -31,38 +48,88 @@ async function isLoggedIn() { -function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { - let socket = getSocket(); - - - -document.addEventListener("visibilitychange", async () => { +async function windowStateHidden() { const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; if (socketId == undefined) return; - if (document.visibilityState === "hidden") { - let userName = await updateUser(); - oldName = userName?.name || undefined; - if (oldName === undefined) return; - localStorage.setItem('oldName', oldName); - socketId.emit('client_left', { - user: userName?.name, - why: 'tab window hidden - socket not dead', - }); - return; - } - if (document.visibilityState === "visible") { - const res = await client.guestLogin(); - let user = await updateUser(); - socketId.emit('client_entered', { - userName: oldName, - user: user?.name, - }); - setTitle('Chat Page'); - return; - } -}); + let userName = await updateUser(); + oldName = userName?.name || undefined; + if (oldName === undefined) return; + localStorage.setItem('oldName', oldName); + socketId.emit('client_left', { + user: userName?.name, + why: 'tab window hidden - socket not dead', + }); + return; +} + + +async function windowStateVisable() { + const socketId = __socket || undefined; + let oldName = localStorage.getItem("oldName") || undefined; + if (socketId == undefined) return; + const res = await client.guestLogin(); + let user = await updateUser(); + socketId.emit('client_entered', { + userName: oldName, + user: user?.name, + }); + setTitle('Chat Page'); + return; +} + + + + + + + + + + +function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { + let socket = getSocket(); + + +// document.addEventListener("visibilitychange", async () => { + +// const socketId = __socket || undefined; +// let oldName = localStorage.getItem("oldName") || undefined; + + + +// if (socketId == undefined) return; +// if (document.visibilityState === "hidden") { +// let userName = await updateUser(); +// oldName = userName?.name || undefined; +// if (oldName === undefined) return; +// localStorage.setItem('oldName', oldName); +// socketId.emit('client_left', { +// user: userName?.name, +// why: 'tab window hidden - socket not dead', +// }); +// return; +// } + + + +// if (document.visibilityState === "visible") { +// const res = await client.guestLogin(); +// let user = await updateUser(); +// socketId.emit('client_entered', { +// userName: oldName, +// user: user?.name, +// }); +// setTitle('Chat Page'); +// return; +// } + + + + + +// }); @@ -133,11 +200,7 @@ document.addEventListener("visibilitychange", async () => { const value = await client.chatTest(); if (value.kind === "success") { console.log(value.payload); - } else if (value.kind === "notLoggedIn") { - console.log('not logged in'); - } else { - console.log('unknown response: ', value); - } + const addMessage = (text: string) => { @@ -147,8 +210,20 @@ document.addEventListener("visibilitychange", async () => { chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; console.log(`Added new message: ${text}`) + return ; }; + if (window.location.pathname === "/app/chat") { + window.addEventListener("focus", () => { + windowStateVisable(); + console.log("%cWindow is focused on /chat", color.green); + }); + + window.addEventListener("blur", () => { + windowStateHidden(); + console.log("%cWindow is not focused on /chat", color.red); + }); + } socket.once('welcome', (data) => { addMessage (`${data.msg} ` + getUser()?.name); @@ -253,6 +328,19 @@ document.addEventListener("visibilitychange", async () => { showError('Failed to login: Unknown error'); } }); + + } else if (value.kind === "notLoggedIn") { + + if (!chatWindow) return; + const messageElement = document.createElement('div-notlog'); + messageElement.textContent = "Not Logged in ...."; + chatWindow.appendChild(messageElement); + chatWindow.scrollTop = chatWindow.scrollHeight; + console.log('not logged in'); + + } else { + console.log('unknown response: ', value); + } } } }; diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 83876fa..d89b248 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -7,6 +7,22 @@ import * as swagger from '@shared/swagger'; import * as utils from '@shared/utils'; import { Server, Socket } from 'socket.io'; +// colors for console.log +export const color = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m', +}; + +// shows address for connection au server transcendance +const session = process.env.SESSION_MANAGER ?? ''; +const part = session.split('/')[1]; +const machineName = part.split('.')[0]; +console.log(color.yellow, 'Connect at : https://' + machineName + ':8888/app/login'); + + declare const __SERVICE_NAME: string; // Global map of clients @@ -52,13 +68,6 @@ const app: FastifyPluginAsync = async (fastify, opts): Promise => { export default app; export { app }; -export const color = { - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - reset: '\x1b[0m', -}; type ClientMessage = { user: string; From c41b32184b5953e9bd502d1c7f742c6728082657 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 27 Nov 2025 18:38:47 +0100 Subject: [PATCH 21/66] Added localhost for dev and connect for machine 42 option --- frontend/src/pages/chat/chat.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 65b5f69..3490985 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -30,7 +30,8 @@ document.addEventListener('ft:pageChange', () => { function getSocket(): Socket { - let addressHost = `wss://${machineHostName}:8888`; + //let addressHost = `wss://${machineHostName}:8888`; + let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { From 09ff8eb5a1c4205ba60920d602be5ee61d848407 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 27 Nov 2025 21:03:25 +0100 Subject: [PATCH 22/66] Re-orga Chat Box --- frontend/src/chat/chat.css | 62 +++++++++++++++++++++---------- frontend/src/pages/chat/chat.html | 53 +++++++++++++++++--------- frontend/src/pages/chat/chat.ts | 2 +- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index b3cb9c3..e94c993 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -2,38 +2,52 @@ .btn-style { @apply - w-[100px] - h-[32px] - border-2 border-black + w-[100px] + h-[32px] + border-1 + border-gray-500 rounded-3xl + bg-gray-500 + text-white + cursor-pointer + shadow-[0_2px_0_0_black] + transition-all hover:bg-blue-200 - active:bg-white - text-black - cursor-pointer; + active:bg-gray-400 + active:translate-y-[1px] + active:shadow-[0_2px_0_0_black]; } .send-btn-style { @apply w-[50px] h-[50px] - border-2 border-black + border-1 + border-gray-500 rounded-3xl hover:bg-blue-200 - active:bg-white - text-black - cursor-pointer; + bg-red-100 + text-red-700 + cursor-pointer + shadow-[0_2px_0_0_black] + transition-all + active:bg-gray-400 + active:translate-y-[1px] + active:shadow-[0_2px_0_0_black];; } .chatbox-style { @apply - w-[600px] - h-[150px] /* increase height if needed */ - p-[10px] - border-1 border-black - shadow-sm + w-[650px] + h-[300px] /* increase height if needed */ + p-[8px] + border-1 + border-black + shadow-2xl text-left - text-gray-800 + text-gray-700 + bg-white rounded-3xl overflow-y-auto whitespace-pre-line @@ -49,9 +63,11 @@ p-[10px] border-1 border-black shadow-sm + flex-1 rounded-3xl - focus:bg-gray-300 - hover:bg-blue-200 + focus:bg-blue-300 + hover:bg-blue-200 + bg-white text-gray-800; } @@ -73,7 +89,7 @@ left-1/2 -translate-x-1/2 -translate-y-1/2 - bg-white w-[650px] + bg-gray-200 w-[850px] p-6 rounded-xl shadow-2xl text-center z-50; @@ -84,6 +100,14 @@ cursor-pointer } +.title { + @apply + text-6xl + font-bold + text-gray-800 +} + + p { @apply text-black diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 45596b5..2f6c2a3 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,18 +1,37 @@
-
- -

- Chat Box -


- - -
-
-
- - -
-
-

From this Chat Box you can send messages to other players

-
-
\ No newline at end of file +
+ +

+ ChatterBoxes 😀😀😀 +


+ + + + +
+ +
+ +
+
+
+ + +
+
+ + +
+

Ping Buddies

+
+

Alice

+

Bob

+

Charlie

+
+
+
+
+ +

From this Chat Box you can send messages to other players

+
+
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 3490985..54d0910 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -14,7 +14,7 @@ const color = { }; -// get the name of the machine useed to connect +// get the name of the machine used to connect const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); From fc6b7042f2dee59aad93a19242946b07b617bc71 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Fri, 28 Nov 2025 14:03:00 +0100 Subject: [PATCH 23/66] added PingBuddies Bar to detect connected Chatters in real time --- frontend/src/chat/chat.css | 27 +++++++++++ frontend/src/pages/chat/chat.html | 18 +++---- frontend/src/pages/chat/chat.ts | 80 +++++++++++++------------------ src/chat/src/app.ts | 7 +-- 4 files changed, 75 insertions(+), 57 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index e94c993..6204f43 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -107,6 +107,33 @@ text-gray-800 } +.ping-box { + @apply + w-[150px] + ml-2 border-1 + border-gray-500 + bg-white + rounded-2xl + p-2 + shadow-md + flex flex-col + gap-1 + h-[350px]; +} + +.ping-title { + @apply + text-sm + font-semibold + text-blue-800; +} + +div-buddies-list { + @apply + text-black + whitespace-pre-wrap; + +} p { @apply diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 2f6c2a3..7c31b84 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -2,7 +2,7 @@

- ChatterBoxes 😀😀😀 + ChatterBoxes


@@ -10,7 +10,7 @@
-
+
@@ -20,13 +20,15 @@
- -
-

Ping Buddies

+ +
+

Ping Buddies

-

Alice

-

Bob

-

Charlie

+
+ +
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 54d0910..6e5369c 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -30,8 +30,8 @@ document.addEventListener('ft:pageChange', () => { function getSocket(): Socket { - //let addressHost = `wss://${machineHostName}:8888`; - let addressHost = `wss://localhost:8888`; + let addressHost = `wss://${machineHostName}:8888`; + // let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -82,55 +82,23 @@ async function windowStateVisable() { +async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { + if (!buddies) return; + const messageElement = document.createElement("div-buddies-list"); + messageElement.textContent = listBuddies + '\n'; + buddies.appendChild(messageElement); + buddies.scrollTop = buddies.scrollHeight; + console.log(`Added buddies: ${listBuddies}`) + return ; - +} function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { let socket = getSocket(); - - -// document.addEventListener("visibilitychange", async () => { - -// const socketId = __socket || undefined; -// let oldName = localStorage.getItem("oldName") || undefined; - - - -// if (socketId == undefined) return; -// if (document.visibilityState === "hidden") { -// let userName = await updateUser(); -// oldName = userName?.name || undefined; -// if (oldName === undefined) return; -// localStorage.setItem('oldName', oldName); -// socketId.emit('client_left', { -// user: userName?.name, -// why: 'tab window hidden - socket not dead', -// }); -// return; -// } - - - -// if (document.visibilityState === "visible") { -// const res = await client.guestLogin(); -// let user = await updateUser(); -// socketId.emit('client_entered', { -// userName: oldName, -// user: user?.name, -// }); -// setTitle('Chat Page'); -// return; -// } - - - - - -// }); @@ -161,8 +129,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log("Recieved data.message.timestamp: ", data.message.timestamp); // Display the message in the chat window const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + const bconnected = document.getElementById('b-help') as HTMLButtonElement; + if (bconnected) { + bconnected.click(); + } + if (chatWindow) { const messageElement = document.createElement("div"); + // if (getUser()?.id !== `${data.message.id}`) { console.log('==================> HERE'); messageElement.textContent = `${data.message.user}: ${data.message.text}`; @@ -197,6 +171,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; const bconnected = document.getElementById('b-help') as HTMLButtonElement; const username = document.getElementById('username') as HTMLDivElement; + const buddies = document.getElementById('div-buddies') as HTMLDivElement; const value = await client.chatTest(); if (value.kind === "success") { @@ -216,6 +191,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (window.location.pathname === "/app/chat") { window.addEventListener("focus", () => { + bconnected.click(); windowStateVisable(); console.log("%cWindow is focused on /chat", color.green); }); @@ -227,11 +203,13 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } socket.once('welcome', (data) => { + bconnected.click(); addMessage (`${data.msg} ` + getUser()?.name); }); // Send button sendButton?.addEventListener("click", () => { + bconnected.click(); if (sendtextbox && sendtextbox.value.trim()) { const msgText = sendtextbox.value.trim(); addMessage(msgText); @@ -259,6 +237,10 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }); + + // setInterval(async () => { + // bconnected.click(); + // }, 5000); // every 1 second // Help Text button bconnected?.addEventListener("click", async () => { @@ -270,21 +252,27 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const res = await client.guestLogin(); let user = await updateUser(); localStorage.setItem("oldName", oldUser); - + buddies.textContent = ""; console.log('USER ', user?.name); if (chatWindow) { - addMessage('@list - lists all connected users in the chat'); + //addMessage('@list - lists all connected users in the chat'); socket.emit('list', { oldUser: oldUser, - user: user?.name + user: user?.name, }); } + }); socket.on('listObj', (list: string) => { console.log('List chat clients connected ', list); addMessage(list); }); + socket.on('listBud', (myBuddies: string) => { + console.log('List buddies connected ', myBuddies); + listBuddies(buddies,myBuddies); + }); + diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index d89b248..2b03457 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -82,6 +82,7 @@ declare module 'fastify' { hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; message: (msg: string) => void; + listBud: (msg: string) => void; testend: (sock_id_client: string) => void; client_entered: (userName: string, user: string) => void; client_left: (userName: string, why: string) => void; @@ -130,8 +131,8 @@ async function onReady(fastify: FastifyInstance) { console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; - io.to(targetSocketId!).emit('listObj', username.user); - + // io.to(targetSocketId!).emit('listObj', username.user); + io.to(targetSocketId!).emit('listBud', username.user); console.log( color.yellow, 'Chat Socket ID:', @@ -163,9 +164,9 @@ async function onReady(fastify: FastifyInstance) { function broadcast(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { for (const s of sockets) { + const clientName = clientChat.get(s.id)?.user; if (s.id !== sender) { // Send REAL JSON object - const clientName = clientChat.get(s.id)?.user; if (clientName !== undefined) { s.emit('MsgObjectServer', { message: data }); } From ffc0651c001517cabc55df180ead0683438a4755 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Fri, 28 Nov 2025 16:40:33 +0100 Subject: [PATCH 24/66] New look --- frontend/src/chat/chat.css | 4 ++ frontend/src/pages/chat/chat.html | 2 +- frontend/src/pages/chat/chat.ts | 68 +++++++++++++++---------------- src/chat/src/app.ts | 5 +-- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 6204f43..95c2bb5 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -1,4 +1,8 @@ @import "tailwindcss"; +@font-face { + font-family: "Nimbus Mono L"; + src: url("/fonts/NimbusMonoL.woff2") format("woff2"); +} .btn-style { @apply diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 7c31b84..dd8c5da 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -2,7 +2,7 @@

- ChatterBoxes + ChatterBox


diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 6e5369c..1636c86 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -18,8 +18,6 @@ const color = { const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); - - let __socket: Socket | undefined = undefined; document.addEventListener('ft:pageChange', () => { if (__socket !== undefined) @@ -28,10 +26,9 @@ document.addEventListener('ft:pageChange', () => { console.log("Page changed"); }) - function getSocket(): Socket { - let addressHost = `wss://${machineHostName}:8888`; - // let addressHost = `wss://localhost:8888`; + // let addressHost = `wss://${machineHostName}:8888`; + let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -47,9 +44,6 @@ async function isLoggedIn() { return getUser() || null; } - - - async function windowStateHidden() { const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; @@ -81,7 +75,6 @@ async function windowStateVisable() { } - async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { if (!buddies) return; @@ -94,6 +87,16 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { } +function waitSocketConnected(socket: Socket): Promise { + return new Promise(resolve => { + if (socket.connected) return resolve(); // already connected + socket.on("connect", () => resolve()); + }); +} +const bconnected = document.getElementById('b-help') as HTMLButtonElement; +if (bconnected) { + bconnected.click(); +} @@ -103,7 +106,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Listen for the 'connect' event - socket.on("connect", () => { + socket.on("connect", async () => { + + await waitSocketConnected(socket); console.log("I AM Connected to the server:", socket.id); const user = getUser()?.name; // Ensure we have a user AND socket is connected @@ -157,7 +162,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn color?: { default: string, hover: string }, }; - // function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { setTitle('Chat Page'); // Listen for the 'connect' event @@ -173,10 +177,10 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const username = document.getElementById('username') as HTMLDivElement; const buddies = document.getElementById('div-buddies') as HTMLDivElement; - const value = await client.chatTest(); - if (value.kind === "success") { - console.log(value.payload); - + chatWindow.textContent = ''; + chatWindow.innerHTML = ''; + buddies.textContent = ''; + buddies.innerHTML = ''; const addMessage = (text: string) => { @@ -191,27 +195,32 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (window.location.pathname === "/app/chat") { window.addEventListener("focus", () => { - bconnected.click(); windowStateVisable(); + bconnected.click(); console.log("%cWindow is focused on /chat", color.green); }); window.addEventListener("blur", () => { windowStateHidden(); + bconnected.click(); console.log("%cWindow is not focused on /chat", color.red); }); } socket.once('welcome', (data) => { + chatWindow.textContent = ''; + chatWindow.innerHTML = ''; + buddies.textContent = ''; + buddies.innerHTML = ''; bconnected.click(); addMessage (`${data.msg} ` + getUser()?.name); }); // Send button sendButton?.addEventListener("click", () => { - bconnected.click(); if (sendtextbox && sendtextbox.value.trim()) { const msgText = sendtextbox.value.trim(); + bconnected.click(); addMessage(msgText); const user = getUser(); if (user && socket?.connected) { @@ -233,14 +242,16 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Clear Text button clearText?.addEventListener("click", () => { if (chatWindow) { + bconnected.click(); chatWindow.innerHTML = ''; } }); - // setInterval(async () => { - // bconnected.click(); - // }, 5000); // every 1 second + bconnected.click(); + setInterval(async () => { + bconnected.click(); + }, 50000); // every 1 second // Help Text button bconnected?.addEventListener("click", async () => { @@ -253,9 +264,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn let user = await updateUser(); localStorage.setItem("oldName", oldUser); buddies.textContent = ""; - console.log('USER ', user?.name); if (chatWindow) { - //addMessage('@list - lists all connected users in the chat'); + // addMessage('@list - lists all connected users in the chat'); socket.emit('list', { oldUser: oldUser, user: user?.name, @@ -317,21 +327,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn showError('Failed to login: Unknown error'); } }); - - } else if (value.kind === "notLoggedIn") { - - if (!chatWindow) return; - const messageElement = document.createElement('div-notlog'); - messageElement.textContent = "Not Logged in ...."; - chatWindow.appendChild(messageElement); - chatWindow.scrollTop = chatWindow.scrollHeight; - console.log('not logged in'); - - } else { - console.log('unknown response: ', value); - } } } }; addRoute('/chat', handleChat, { bypass_auth: true }); addRoute('/chat/', handleChat, { bypass_auth: true }); + diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 2b03457..c4e6677 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -157,15 +157,14 @@ async function onReady(fastify: FastifyInstance) { socketId, ); } - return count; } function broadcast(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { for (const s of sockets) { - const clientName = clientChat.get(s.id)?.user; if (s.id !== sender) { + const clientName = clientChat.get(s.id)?.user; // Send REAL JSON object if (clientName !== undefined) { s.emit('MsgObjectServer', { message: data }); @@ -229,7 +228,7 @@ async function onReady(fastify: FastifyInstance) { if (userFromFrontend.oldUser !== userFromFrontend.user) { console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset); - if (client === null) { + if (client?.user === null) { console.log('ERROR: clientName is NULL'); return; }; From 438cb10abb162d452624a543f9275df60bccf2b8 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Sat, 29 Nov 2025 14:16:20 +0100 Subject: [PATCH 25/66] Bug fixed multiple notification when user leaves by the side menu and comes back on chat without logining out --- frontend/src/pages/chat/chat.ts | 59 +++++++++++++++++++++------------ frontend/src/routing/index.ts | 1 - src/chat/src/app.ts | 16 +++++---- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 1636c86..a0c3faf 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -27,8 +27,8 @@ document.addEventListener('ft:pageChange', () => { }) function getSocket(): Socket { - // let addressHost = `wss://${machineHostName}:8888`; - let addressHost = `wss://localhost:8888`; + let addressHost = `wss://${machineHostName}:8888`; + // let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -63,9 +63,12 @@ async function windowStateHidden() { async function windowStateVisable() { const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; - if (socketId == undefined) return; + console.log("%coldName :'" + oldName + "'", color.green); + + if (socketId === undefined || oldName === undefined) {console.log("%SOCKET ID", color.red); return;} const res = await client.guestLogin(); let user = await updateUser(); + console.log("%cUserName :'" + user?.name + "'", color.green); socketId.emit('client_entered', { userName: oldName, user: user?.name, @@ -101,10 +104,10 @@ if (bconnected) { function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { + + let socket = getSocket(); - - - + // Listen for the 'connect' event socket.on("connect", async () => { @@ -163,6 +166,32 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }; + let toggle = false + window.addEventListener("focus", () => { + if (window.location.pathname === "/app/chat" && !toggle) { + // bconnected.click(); + console.log("%cWindow is focused on /chat:" + socket.id, color.green); + if (socket.id) + windowStateVisable(); + toggle = true; + } + }); + + window.addEventListener("blur", () => { + // if (window.location.pathname !== "/app/chat" && !toggle) { + // // bconnected.click(); + // console.log("%cWindow is not focused on /chat", color.red); + + if (socket.id) + windowStateHidden(); + // }); + toggle = false; + // } + }); + + + + setTitle('Chat Page'); // Listen for the 'connect' event return { @@ -193,20 +222,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn return ; }; - if (window.location.pathname === "/app/chat") { - window.addEventListener("focus", () => { - windowStateVisable(); - bconnected.click(); - console.log("%cWindow is focused on /chat", color.green); - }); - - window.addEventListener("blur", () => { - windowStateHidden(); - bconnected.click(); - console.log("%cWindow is not focused on /chat", color.red); - }); - } - socket.once('welcome', (data) => { chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -214,7 +229,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn buddies.innerHTML = ''; bconnected.click(); addMessage (`${data.msg} ` + getUser()?.name); - }); + }); // Send button sendButton?.addEventListener("click", () => { @@ -331,5 +346,5 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }; addRoute('/chat', handleChat, { bypass_auth: true }); -addRoute('/chat/', handleChat, { bypass_auth: true }); +// addRoute('/chat/', handleChat, { bypass_auth: true }); diff --git a/frontend/src/routing/index.ts b/frontend/src/routing/index.ts index 9e504db..0c0b9a4 100644 --- a/frontend/src/routing/index.ts +++ b/frontend/src/routing/index.ts @@ -196,7 +196,6 @@ export async function handleRoute() { return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`) const app = document.getElementById('app')!; document.dispatchEvent(new CustomEvent('ft:pageChange' as any, {} as any) as any); - document.dispatchEvent(new CustomEvent('ft:tabChange' as any, {} as any) as any); let ret = await executeRouteHandler(route_handler, window.location.pathname, args) app.innerHTML = ret.html; if (ret.postInsert) { diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index c4e6677..245d2d6 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -168,6 +168,7 @@ async function onReady(fastify: FastifyInstance) { // Send REAL JSON object if (clientName !== undefined) { s.emit('MsgObjectServer', { message: data }); + console.log(color.green, 'Name Sender', clientChat); } console.log(' Target window socket ID:', s.id); console.log(' Target window ID:', [...s.rooms]); @@ -309,24 +310,24 @@ async function onReady(fastify: FastifyInstance) { const userNameFromFrontend = data?.userName || null; const userFromFrontend = data?.user || null; let clientName = clientChat.get(socket.id)?.user || null; - const client = clientChat.get(socket.id) || null; + // const client = clientChat.get(socket.id) || null; let text = 'is back in the chat'; if (clientName === null) { console.log('ERROR: clientName is NULL'); return; }; - if (client === null) { - console.log('ERROR: client is NULL'); return; - }; + // if (client === null) { + // console.log('ERROR: client is NULL'); return; + // }; if (userNameFromFrontend !== userFromFrontend) { text = `'is back in the chat, I used to be called '${userNameFromFrontend}`; clientName = userFromFrontend; if (clientName === null) { console.log('ERROR: clientName is NULL'); return; }; - if (client) { - client.user = clientName; - } + // if (client) { + // client.user = clientName; + // } } console.log( color.green, @@ -346,5 +347,6 @@ async function onReady(fastify: FastifyInstance) { broadcast(obj, obj.SenderWindowID); } }); + }); } From 270c3297fd999307c1914b10db384efc42d285e5 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Sat, 29 Nov 2025 16:05:18 +0100 Subject: [PATCH 26/66] added blocked database --- src/@shared/src/database/index.ts | 4 +- src/@shared/src/database/init.sql | 14 ++++++ src/@shared/src/database/mixin/blocked.ts | 55 +++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/@shared/src/database/mixin/blocked.ts diff --git a/src/@shared/src/database/index.ts b/src/@shared/src/database/index.ts index 597efac..ea111db 100644 --- a/src/@shared/src/database/index.ts +++ b/src/@shared/src/database/index.ts @@ -4,11 +4,13 @@ import { FastifyInstance, FastifyPluginAsync } from 'fastify'; import { isNullish } from '@shared/utils'; import { Database as DbImpl } from './mixin/_base'; import { IUserDb, UserImpl } from './mixin/user'; +import { IBlockedDb, BlockedImpl } from './mixin/blocked'; Object.assign(DbImpl.prototype, UserImpl); +Object.assign(DbImpl.prototype, BlockedImpl); -export interface Database extends DbImpl, IUserDb { } +export interface Database extends DbImpl, IUserDb, IBlockedDb { } // When using .decorate you have to specify added properties for Typescript declare module 'fastify' { diff --git a/src/@shared/src/database/init.sql b/src/@shared/src/database/init.sql index b53675b..b4e1415 100644 --- a/src/@shared/src/database/init.sql +++ b/src/@shared/src/database/init.sql @@ -7,3 +7,17 @@ CREATE TABLE IF NOT EXISTS user ( guest INTEGER NOT NULL DEFAULT 0, oauth2 TEXT DEFAULT NULL ); + + +CREATE TABLE IF NOT EXISTS blocked ( + id INTEGER PRIMARY KEY NOT NULL, + user TEXT NOT NULL, + blocked TEXT NOT NULL, + + FOREIGN KEY(user) REFERENCES user(id); + FOREIGN KEY(blocked) REFERENCES user(id); +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair + ON blocked(user, blocked); + diff --git a/src/@shared/src/database/mixin/blocked.ts b/src/@shared/src/database/mixin/blocked.ts new file mode 100644 index 0000000..2f3c9f4 --- /dev/null +++ b/src/@shared/src/database/mixin/blocked.ts @@ -0,0 +1,55 @@ +import { isNullish } from '@shared/utils'; +import type { Database } from './_base'; +import { UserId } from './user'; + +// never use this directly + +// describe every function in the object +export interface IBlockedDb extends Database { + getBlockedUserFor(id: UserId): BlockedData[], + addBlockedUserFor(id: UserId, blocked: UserId): void, + removeBlockedUserFor(id: UserId, blocked: UserId): void, + unblockAllUserFor(id: UserId): void, +}; + +export const BlockedImpl: Omit = { + getBlockedUserFor(this: IBlockedDb, id: UserId): BlockedData[] { + const query = this.prepare('SELECT * FROM blocked WHERE user = @id'); + const data = query.all({ id }) as Partial[]; + return data.map(blockedFromRow).filter(b => !isNullish(b)); + }, + + unblockAllUserFor(this: IBlockedDb, id: UserId): void { + this.prepare('DELETE FROM blocked WHERE user = @id').run({ id }); + }, + addBlockedUserFor(this: IBlockedDb, id: UserId, blocked: UserId): void { + this.prepare('INSERT OR IGNORE INTO blocked (user, blocked) VALUES (@id, @blocked)').run({ id, blocked }); + }, + removeBlockedUserFor(this: IBlockedDb, id: UserId, blocked: UserId): void { + this.prepare('DELETE FROM blocked WHERE user = @id AND blocked = @blocked').run({ id, blocked }); + }, +}; + +export type BlockedId = number & { readonly __brand: unique symbol }; + +export type BlockedData = { + readonly id: BlockedId; + readonly user: UserId; + readonly blocked: UserId; +}; + +/** + * Get a blocked from a row + * + * @param row The data from sqlite + * + * @returns The blocked if it exists, undefined otherwise + */ +export function blockedFromRow(row?: Partial): BlockedData | undefined { + if (isNullish(row)) return undefined; + if (isNullish(row.id)) return undefined; + if (isNullish(row.user)) return undefined; + if (isNullish(row.blocked)) return undefined; + + return row as BlockedData; +} From 8fd3063420ea9941677015e31dd9deb4eb36556e Mon Sep 17 00:00:00 2001 From: NigeParis Date: Sat, 29 Nov 2025 17:47:04 +0100 Subject: [PATCH 27/66] Bug - db fixed --- frontend/src/pages/chat/chat.ts | 30 ++++++------------------------ src/@shared/src/database/init.sql | 4 ++-- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index a0c3faf..8dfeefc 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -10,7 +10,7 @@ const color = { green: 'color: green; font-weight: bold;', yellow: 'color: orange; font-weight: bold;', blue: 'color: blue; font-weight: bold;', - reset: '', // not needed in browser + reset: '', }; @@ -27,8 +27,8 @@ document.addEventListener('ft:pageChange', () => { }) function getSocket(): Socket { - let addressHost = `wss://${machineHostName}:8888`; - // let addressHost = `wss://localhost:8888`; + // let addressHost = `wss://${machineHostName}:8888`; + let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -144,14 +144,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (chatWindow) { const messageElement = document.createElement("div"); - - // if (getUser()?.id !== `${data.message.id}`) { - console.log('==================> HERE'); messageElement.textContent = `${data.message.user}: ${data.message.text}`; - // } else { - // console.log('==================>AND HERE'); - // messageElement.textContent = `here`; - // } chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; } @@ -178,20 +171,13 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); window.addEventListener("blur", () => { - // if (window.location.pathname !== "/app/chat" && !toggle) { - // // bconnected.click(); - // console.log("%cWindow is not focused on /chat", color.red); - + bconnected.click(); + console.log("%cWindow is not focused on /chat", color.red); if (socket.id) windowStateHidden(); - // }); - toggle = false; - // } + toggle = false; }); - - - setTitle('Chat Page'); // Listen for the 'connect' event return { @@ -299,9 +285,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); - - - // Enter key to send message sendtextbox!.addEventListener('keydown', (event) => { if (event.key === 'Enter') { @@ -346,5 +329,4 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }; addRoute('/chat', handleChat, { bypass_auth: true }); -// addRoute('/chat/', handleChat, { bypass_auth: true }); diff --git a/src/@shared/src/database/init.sql b/src/@shared/src/database/init.sql index b4e1415..b77edfe 100644 --- a/src/@shared/src/database/init.sql +++ b/src/@shared/src/database/init.sql @@ -14,8 +14,8 @@ CREATE TABLE IF NOT EXISTS blocked ( user TEXT NOT NULL, blocked TEXT NOT NULL, - FOREIGN KEY(user) REFERENCES user(id); - FOREIGN KEY(blocked) REFERENCES user(id); + FOREIGN KEY(user) REFERENCES user(id) + FOREIGN KEY(blocked) REFERENCES user(id) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair From 4b04aec780fad409cc4ec011f33994da5c8c0ca0 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 1 Dec 2025 10:05:49 +0100 Subject: [PATCH 28/66] Bugs - review co-pilote - returned before changing code --- frontend/src/pages/chat/chat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 8dfeefc..5706bb3 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -171,7 +171,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); window.addEventListener("blur", () => { - bconnected.click(); + //bconnected.click(); console.log("%cWindow is not focused on /chat", color.red); if (socket.id) windowStateHidden(); @@ -252,7 +252,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn bconnected.click(); setInterval(async () => { bconnected.click(); - }, 50000); // every 1 second + }, 10000); // every 10 second // Help Text button bconnected?.addEventListener("click", async () => { From 6a53c5266f5baca992e8137ede57b9806cdb3e4a Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 1 Dec 2025 12:16:08 +0100 Subject: [PATCH 29/66] added Quit Chat Button --- frontend/src/pages/chat/chat.html | 5 ++- frontend/src/pages/chat/chat.ts | 42 +++++++++++++++++--- src/chat/src/app.ts | 66 ++++++++++++++++++++++--------- 3 files changed, 88 insertions(+), 25 deletions(-) diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index dd8c5da..e6d9e92 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -5,6 +5,7 @@ ChatterBox
+ @@ -12,9 +13,9 @@
-
+
-
+
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 5706bb3..864608e 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -14,6 +14,9 @@ const color = { }; + + + // get the name of the machine used to connect const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); @@ -101,6 +104,14 @@ if (bconnected) { bconnected.click(); } +function logout(socket: Socket) { + socket.emit("logout"); // notify server + socket.disconnect(); // actually close the socket + localStorage.clear(); + if (__socket !== undefined) + __socket.close(); +// window.location.href = "/login"; +} function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -108,6 +119,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn let socket = getSocket(); + + // Listen for the 'connect' event socket.on("connect", async () => { @@ -151,6 +164,16 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log("Getuser():", getUser()); }); + + + + socket.on('logout', () => { + const bquit = document.getElementById('b-quit') as HTMLDivElement | null; + if (bquit instanceof HTMLDivElement) { + bquit.click(); + } + }); + type Providers = { name: string, display_name: string, @@ -191,6 +214,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const bconnected = document.getElementById('b-help') as HTMLButtonElement; const username = document.getElementById('username') as HTMLDivElement; const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const bquit = document.getElementById('b-quit') as HTMLDivElement; chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -240,16 +264,28 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); + + // Clear Text button clearText?.addEventListener("click", () => { + if (chatWindow) { bconnected.click(); chatWindow.innerHTML = ''; } }); + bquit?.addEventListener('click', () => { + if (socket) { + logout(socket); + setTitle('Chat Page'); + bconnected.click(); + } else { + getSocket(); + } + }); + - bconnected.click(); setInterval(async () => { bconnected.click(); }, 10000); // every 10 second @@ -274,10 +310,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }); - socket.on('listObj', (list: string) => { - console.log('List chat clients connected ', list); - addMessage(list); - }); socket.on('listBud', (myBuddies: string) => { console.log('List buddies connected ', myBuddies); diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 245d2d6..e426758 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -131,7 +131,6 @@ async function onReady(fastify: FastifyInstance) { console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; - // io.to(targetSocketId!).emit('listObj', username.user); io.to(targetSocketId!).emit('listBud', username.user); console.log( color.yellow, @@ -160,23 +159,32 @@ async function onReady(fastify: FastifyInstance) { return count; } - function broadcast(data: ClientMessage, sender?: string) { - fastify.io.fetchSockets().then((sockets) => { - for (const s of sockets) { - if (s.id !== sender) { - const clientName = clientChat.get(s.id)?.user; - // Send REAL JSON object - if (clientName !== undefined) { - s.emit('MsgObjectServer', { message: data }); - console.log(color.green, 'Name Sender', clientChat); - } - console.log(' Target window socket ID:', s.id); - console.log(' Target window ID:', [...s.rooms]); - console.log(' Sender window ID:', sender ? sender : 'none'); - } +function broadcast(data: ClientMessage, sender?: string) { + fastify.io.fetchSockets().then((sockets) => { + for (const s of sockets) { + + // Skip sender's own socket + if (s.id === sender) continue; + + // Get client name from map + const clientInfo = clientChat.get(s.id); + + if (!clientInfo?.user) { + console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); + continue; } - }); - } + + // Emit structured JSON object + s.emit("MsgObjectServer", { message: data }); + + // Debug logs + console.log(color.green, "Broadcast to:", clientInfo.user); + console.log(" Target socket ID:", s.id); + console.log(" Target rooms:", [...s.rooms]); + console.log(" Sender socket ID:", sender ?? "none"); + } + }); +} fastify.io.on('connection', (socket: Socket) => { @@ -229,7 +237,7 @@ async function onReady(fastify: FastifyInstance) { if (userFromFrontend.oldUser !== userFromFrontend.user) { console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset); - if (client?.user === null) { + if (client?.user === null) { console.log('ERROR: clientName is NULL'); return; }; @@ -256,6 +264,28 @@ async function onReady(fastify: FastifyInstance) { } }); + socket.on("logout", () => { + const clientInfo = clientChat.get(socket.id); + const clientName = clientInfo?.user; + + if (!clientName) return; + console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); + const obj = { + type: "chat" as const, + user: clientName, + token: "", + text: "LEFT the chat", + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + broadcast(obj, socket.id); + // Optional: remove from map + clientChat.delete(socket.id); + // Ensure socket is fully disconnected + if (socket.connected) socket.disconnect(true); + }); + + socket.on('disconnecting', (reason) => { const clientName = clientChat.get(socket.id)?.user || null; From 05a9c58de58199b60f7c762872369eec4d54a437 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 1 Dec 2025 18:46:38 +0100 Subject: [PATCH 30/66] playing wiith database Blocked Users --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 22 +- frontend/src/pages/chat/chat.ts | 11 +- src/@shared/package.json | 4 +- src/@shared/src/database/mixin/blocked.ts | 21 +- src/@shared/src/database/mixin/user.ts | 11 + src/auth/package.json | 6 +- src/chat/package.json | 8 +- src/chat/src/routes/nginx-chat.ts | 94 +++++++- src/icons/package.json | 4 +- src/package.json | 4 +- src/pnpm-lock.yaml | 266 +++++++++++----------- src/user/package.json | 6 +- 13 files changed, 291 insertions(+), 168 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9e51cb2..04bd252 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@types/js-cookie": "^3.0.6", "typescript": "~5.9.3", - "vite": "^7.2.4", + "vite": "^7.2.6", "vite-tsconfig-paths": "^5.1.4" }, "dependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a490db1..6addf30 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.2.4(jiti@2.6.1)(lightningcss@1.30.2)) + version: 4.1.17(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)) js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -31,11 +31,11 @@ importers: specifier: ~5.9.3 version: 5.9.3 vite: - specifier: ^7.2.4 - version: 7.2.4(jiti@2.6.1)(lightningcss@1.30.2) + specifier: ^7.2.6 + version: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(jiti@2.6.1)(lightningcss@1.30.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)) packages: @@ -635,8 +635,8 @@ packages: vite: optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -919,12 +919,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.4(jiti@2.6.1)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.1.17(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.4(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) '@types/estree@1.0.8': {} @@ -1139,18 +1139,18 @@ snapshots: typescript@5.9.3: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(jiti@2.6.1)(lightningcss@1.30.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.4(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.4(jiti@2.6.1)(lightningcss@1.30.2): + vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 864608e..f03088e 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -184,7 +184,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn let toggle = false window.addEventListener("focus", () => { - if (window.location.pathname === "/app/chat" && !toggle) { + if (window.location.pathname === "" && !toggle) { // bconnected.click(); console.log("%cWindow is focused on /chat:" + socket.id, color.green); if (socket.id) @@ -222,6 +222,15 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn buddies.innerHTML = ''; + const value = await client.chatTest(); + if (value.kind === "success") { + console.log(value.payload); + } else if (value.kind === "notLoggedIn") { + console.log('not logged in'); + } else { + console.log('unknown response: ', value); + } + const addMessage = (text: string) => { if (!chatWindow) return; const messageElement = document.createElement("div-test"); diff --git a/src/@shared/package.json b/src/@shared/package.json index dad8675..15f2935 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -20,8 +20,8 @@ "fastify-plugin": "^5.1.0", "joi": "^18.0.2", "otp": "^1.1.2", - "typebox": "^1.0.56", - "uuidv7": "^1.0.2" + "typebox": "^1.0.59", + "uuidv7": "^1.1.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", diff --git a/src/@shared/src/database/mixin/blocked.ts b/src/@shared/src/database/mixin/blocked.ts index 2f3c9f4..13db21d 100644 --- a/src/@shared/src/database/mixin/blocked.ts +++ b/src/@shared/src/database/mixin/blocked.ts @@ -2,7 +2,6 @@ import { isNullish } from '@shared/utils'; import type { Database } from './_base'; import { UserId } from './user'; -// never use this directly // describe every function in the object export interface IBlockedDb extends Database { @@ -10,6 +9,8 @@ export interface IBlockedDb extends Database { addBlockedUserFor(id: UserId, blocked: UserId): void, removeBlockedUserFor(id: UserId, blocked: UserId): void, unblockAllUserFor(id: UserId): void, + getAllBlockedUsers(this: IBlockedDb): BlockedData[] | undefined, + }; export const BlockedImpl: Omit = { @@ -28,6 +29,23 @@ export const BlockedImpl: Omit = { removeBlockedUserFor(this: IBlockedDb, id: UserId, blocked: UserId): void { this.prepare('DELETE FROM blocked WHERE user = @id AND blocked = @blocked').run({ id, blocked }); }, + + + /** + * Get all blocked user + * + * @param + * + * @returns The list of users if it exists, undefined otherwise + */ + getAllBlockedUsers(this: IBlockedDb): BlockedData[] { + const rows = this.prepare('SELECT * FROM blocked').all() as Partial[]; + + return rows + .map(row => blockedFromRow(row)) + .filter((u): u is BlockedData => u !== undefined); + }, + }; export type BlockedId = number & { readonly __brand: unique symbol }; @@ -46,6 +64,7 @@ export type BlockedData = { * @returns The blocked if it exists, undefined otherwise */ export function blockedFromRow(row?: Partial): BlockedData | undefined { + console.log('HELLO ?????', row); if (isNullish(row)) return undefined; if (isNullish(row.id)) return undefined; if (isNullish(row.user)) return undefined; diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index 558bfee..a931aa8 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -18,6 +18,8 @@ export interface IUserDb extends Database { ensureUserOtpSecret(id: UserId): string | undefined, deleteUserOtpSecret(id: UserId): void, getAllUserFromProvider(provider: string): User[] | undefined, + getAllUsers(this: IUserDb): User[] | undefined, + }; export const UserImpl: Omit = { @@ -36,6 +38,15 @@ export const UserImpl: Omit = { ); }, + getAllUsers(this: IUserDb): User[] { + const rows = this.prepare('SELECT * FROM user').all() as Partial[]; + + return rows + .map(row => userFromRow(row)) + .filter((u): u is User => u !== undefined); + }, + + /** * Get a user from a raw [UserId] * diff --git a/src/auth/package.json b/src/auth/package.json index adb5e2e..9c9dd49 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -21,18 +21,18 @@ "@fastify/autoload": "^6.3.1", "@fastify/formbody": "^8.0.2", "@fastify/multipart": "^9.3.0", - "@fastify/sensible": "^6.0.3", + "@fastify/sensible": "^6.0.4", "@fastify/static": "^8.3.0", "confbox": "^0.2.2", "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.56" + "typebox": "^1.0.59" }, "devDependencies": { "@types/node": "^22.19.1", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.4", + "vite": "^7.2.6", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/chat/package.json b/src/chat/package.json index 7a504d7..56396ad 100644 --- a/src/chat/package.json +++ b/src/chat/package.json @@ -21,18 +21,18 @@ "@fastify/autoload": "^6.3.1", "@fastify/formbody": "^8.0.2", "@fastify/multipart": "^9.3.0", - "@fastify/sensible": "^6.0.3", + "@fastify/sensible": "^6.0.4", "@fastify/static": "^8.3.0", "@fastify/websocket": "^11.2.0", - "@sinclair/typebox": "^0.34.41", "fastify": "^5.6.2", "fastify-plugin": "^5.1.0", - "socket.io": "^4.8.1" + "socket.io": "^4.8.1", + "typebox": "^1.0.59" }, "devDependencies": { "@types/node": "^22.19.1", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.4", + "vite": "^7.2.6", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/chat/src/routes/nginx-chat.ts b/src/chat/src/routes/nginx-chat.ts index d3aaebb..e6dd4df 100644 --- a/src/chat/src/routes/nginx-chat.ts +++ b/src/chat/src/routes/nginx-chat.ts @@ -1,6 +1,27 @@ import { FastifyPluginAsync } from 'fastify'; import { MakeStaticResponse, typeResponse } from '@shared/utils'; -import { Type } from '@sinclair/typebox'; +import { Type } from 'typebox'; +import { UserId } from '@shared/database/mixin/user'; +import { Server } from 'socket.io'; + +// colors for console.log +export const color = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m', +}; + +// Global map of clients +// key = socket, value = clientname +interface ClientInfo { + user: string; + lastSeen: number; +} + +const clientChat = new Map(); + export const ChatRes = { 200: typeResponse('success', 'chat.success', { @@ -12,6 +33,56 @@ export const ChatRes = { export type ChatResType = MakeStaticResponse; + +function connectedUser(io: Server | undefined, targetSocketId?: string): number { + let count = 0; + + // Track unique usernames (avoid duplicates) + const seenUsers = new Set(); + + for (const [socketId, info] of clientChat) { + + // Validate entry + if (!info || typeof info.user !== "string" || info.user.trim() === "") { + clientChat.delete(socketId); + continue; + } + + const username = info.user; + + // Validate socket exists if io is passed + if (io) { + const socket = io.sockets.sockets.get(socketId); + + // Remove disconnected sockets + if (!socket || socket.disconnected) { + clientChat.delete(socketId); + continue; + } + } + + // Skip duplicates + if (seenUsers.has(username)) + continue; + + seenUsers.add(username); + count++; + + // Send to target only + if (io && targetSocketId) { + io.to(targetSocketId).emit("listBud", username); + } + + console.log(color.yellow, "Client:", color.reset, username); + console.log(color.yellow, "Socket ID:", color.reset, socketId); + } + + return count; +} + + + + const route: FastifyPluginAsync = async (fastify): Promise => { fastify.get( '/api/chat/test', @@ -23,8 +94,25 @@ const route: FastifyPluginAsync = async (fastify): Promise => { config: { requireAuth: true }, }, async (req, res) => { - // console.log('/api/chat/test called =================>'); - res.makeResponse(200, 'success', 'CCChat.success', { name: 'My_namw', 'id': req.authUser!.id, guest: false }); + + + + let users = fastify.db.getAllUsers(); + console.log("ALL USERS EVER CONNECTED:", users); + + if (!users) return; + for (const user of users) { + console.log(color.yellow, "USER:", user.name); + } + + // const usersBlocked = fastify.db.getAllBlockedUsers(); + // console.log(color.red, "ALL BLOCKED USERS:", usersBlocked); + fastify.db.addBlockedUserFor(users[0].id, users[1].id) + let usersBlocked2; + usersBlocked2 = fastify.db.getAllBlockedUsers(); + console.log(color.green, "ALL BLOCKED USERS:", usersBlocked2); + + res.makeResponse(200, 'success', 'CCChat.success', { name: 'name', 'id': req.authUser!.id, guest: false }); }, ); }; diff --git a/src/icons/package.json b/src/icons/package.json index 1952efc..3bef3d5 100644 --- a/src/icons/package.json +++ b/src/icons/package.json @@ -20,7 +20,7 @@ "@fastify/autoload": "^6.3.1", "@fastify/formbody": "^8.0.2", "@fastify/multipart": "^9.3.0", - "@fastify/sensible": "^6.0.3", + "@fastify/sensible": "^6.0.4", "@fastify/static": "^8.3.0", "fastify": "^5.6.2", "fastify-cli": "^7.4.1", @@ -31,7 +31,7 @@ "devDependencies": { "@types/node": "^22.19.1", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.4", + "vite": "^7.2.6", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/package.json b/src/package.json index c833a12..4858a48 100644 --- a/src/package.json +++ b/src/package.json @@ -33,10 +33,10 @@ "openapi-typescript": "^7.10.1", "typescript": "^5.9.3", "typescript-eslint": "^8.48.0", - "vite": "^7.2.4" + "vite": "^7.2.6" }, "dependencies": { - "@redocly/cli": "^2.12.0", + "@redocly/cli": "^2.12.1", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index bcdb1ae..a7690b6 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.12.0 - version: 2.12.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.1 + version: 2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -49,8 +49,8 @@ importers: specifier: ^8.48.0 version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1)(yaml@2.8.2) '@shared': dependencies: @@ -88,11 +88,11 @@ importers: specifier: ^1.1.2 version: 1.1.2 typebox: - specifier: ^1.0.56 - version: 1.0.56 + specifier: ^1.0.59 + version: 1.0.59 uuidv7: - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.1.0 + version: 1.1.0 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -113,8 +113,8 @@ importers: specifier: ^9.3.0 version: 9.3.0 '@fastify/sensible': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^6.0.4 + version: 6.0.4 '@fastify/static': specifier: ^8.3.0 version: 8.3.0 @@ -131,8 +131,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.56 - version: 1.0.56 + specifier: ^1.0.59 + version: 1.0.59 devDependencies: '@types/node': specifier: ^22.19.1 @@ -141,11 +141,11 @@ importers: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@22.19.1)(yaml@2.8.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) chat: dependencies: @@ -159,17 +159,14 @@ importers: specifier: ^9.3.0 version: 9.3.0 '@fastify/sensible': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^6.0.4 + version: 6.0.4 '@fastify/static': specifier: ^8.3.0 version: 8.3.0 '@fastify/websocket': specifier: ^11.2.0 version: 11.2.0 - '@sinclair/typebox': - specifier: ^0.34.41 - version: 0.34.41 fastify: specifier: ^5.6.2 version: 5.6.2 @@ -179,6 +176,9 @@ importers: socket.io: specifier: ^4.8.1 version: 4.8.1 + typebox: + specifier: ^1.0.59 + version: 1.0.59 devDependencies: '@types/node': specifier: ^22.19.1 @@ -187,11 +187,11 @@ importers: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@22.19.1)(yaml@2.8.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) icons: dependencies: @@ -205,8 +205,8 @@ importers: specifier: ^9.3.0 version: 9.3.0 '@fastify/sensible': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^6.0.4 + version: 6.0.4 '@fastify/static': specifier: ^8.3.0 version: 8.3.0 @@ -233,11 +233,11 @@ importers: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@22.19.1)(yaml@2.8.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) user: dependencies: @@ -251,8 +251,8 @@ importers: specifier: ^9.3.0 version: 9.3.0 '@fastify/sensible': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^6.0.4 + version: 6.0.4 '@fastify/static': specifier: ^8.3.0 version: 8.3.0 @@ -266,8 +266,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.56 - version: 1.0.56 + specifier: ^1.0.59 + version: 1.0.59 devDependencies: '@types/node': specifier: ^22.19.1 @@ -276,11 +276,11 @@ importers: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@22.19.1)(yaml@2.8.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) packages: @@ -489,8 +489,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.1': @@ -557,8 +557,8 @@ packages: '@fastify/send@4.1.0': resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} - '@fastify/sensible@6.0.3': - resolution: {integrity: sha512-Iyn8698hp/e5+v8SNBBruTa7UfrMEP52R16dc9jMpqSyEcPsvWFQo+R6WwHCUnJiLIsuci2ZoEZ7ilrSSCPIVg==} + '@fastify/sensible@6.0.4': + resolution: {integrity: sha512-1vxcCUlPMew6WroK8fq+LVOwbsLtX+lmuRuqpcp6eYqu6vmkLwbKTdBWAZwbeaSgCfW4tzUpTIHLLvTiQQ1BwQ==} '@fastify/static@8.3.0': resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==} @@ -940,14 +940,11 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@redocly/ajv@8.11.4': - resolution: {integrity: sha512-77MhyFgZ1zGMwtCpqsk532SJEc3IJmSOXKTCeWoMTAvPnQOkuOgxEip1n5pG5YX1IzCTJ4kCvPKr8xYyzWFdhg==} - '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.12.0': - resolution: {integrity: sha512-/q8RnBe+Duo+XYFCG8LnaD0kroGZ8MoS6575Xq59tCgjaCL16F+pZZ75xNBU2oXfEypJClNz/6ilc2G0q1+tlw==} + '@redocly/cli@2.12.1': + resolution: {integrity: sha512-XGD28QjjZEzN+J9WOROzw4fHNi+Fyw/gCyDZDgI4nX4j9gEBT1PcxN75wWpMoDGHKAUj8ghrhMHtfQoUuR90zg==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true @@ -961,12 +958,12 @@ packages: resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.12.0': - resolution: {integrity: sha512-RsVwmRD0KhyJbR8acIeU98ce6N+/YCuLJf6IGN+2SOsbwnDhnI5MG0TFV9D7URK/ukEewaNA701dVYsoP1VtRQ==} + '@redocly/openapi-core@2.12.1': + resolution: {integrity: sha512-xMlKf4dnZsxP3JYBNZFsMNBJqVxWlwLuyGLhGc36hXw50YOla1UjrVZ5psIyzLXgUPI3QJDA1XmGcJ8rcex/ow==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.12.0': - resolution: {integrity: sha512-mrYrfE81shSRS96ygXaRiSithV4Fe4Y7XlSYLSTfM8Lo3YAz7Geirg7HZ5fNFsI+hdW05ZuQewqpKL8XLwaAeA==} + '@redocly/respect-core@2.12.1': + resolution: {integrity: sha512-ADm+JMHWGYeOwzdGEQ8CYKjmMBLU0ycZTwJbCkQsUulXSNkNA7GzA8lrMM2+I8cPMRk25G5PmtfAR7U+a0o1ew==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -1079,9 +1076,6 @@ packages: cpu: [x64] os: [win32] - '@sinclair/typebox@0.34.41': - resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} - '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -1209,14 +1203,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1479,6 +1465,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -1726,8 +1716,8 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - fast-copy@3.0.2: - resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-copy@4.0.0: + resolution: {integrity: sha512-/oA0gx1xyXE9R2YlV4FXwZJXngFdm9Du0zN8FhY38jnLkhp1u35h6bCyKgRhlsA6C9I+1vfXE4KISdt7xc6M9w==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -2019,8 +2009,8 @@ packages: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-binary-path@2.1.0: @@ -2187,8 +2177,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@7.18.3: @@ -2213,9 +2203,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} @@ -2225,10 +2215,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -2521,8 +2519,11 @@ packages: pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - pino-pretty@13.1.2: - resolution: {integrity: sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} hasBin: true pino-std-serializers@7.0.0: @@ -3021,12 +3022,12 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typebox@1.0.56: - resolution: {integrity: sha512-KMd1DJnIRqLUzAicpFmGqgmt+/IePCEmT/Jtywyyyn0hK6+dupQnxm7OAIn/cL/vu22jKi1XvDjDhrpatZ46kA==} + typebox@1.0.59: + resolution: {integrity: sha512-D8pTcn4yPzUb34OWFyPVITP1fMZjJXV5n7tT5ss/BxHSnzfuMK4rQEFunpk1UOwq9lMCOed2BJ3GXGoIBKl7Rw==} typescript-eslint@8.48.0: resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} @@ -3071,9 +3072,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - uri-js-replace@1.0.1: - resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3088,8 +3086,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuidv7@1.0.2: - resolution: {integrity: sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==} + uuidv7@1.1.0: + resolution: {integrity: sha512-2VNnOC0+XQlwogChUDzy6pe8GQEys9QFZBGOh54l6qVfwoCUwwRvk7rDTgaIsRgsF5GFa5oiNg8LqXE3jofBBg==} hasBin: true vary@1.1.2: @@ -3104,8 +3102,8 @@ packages: vite: optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3238,8 +3236,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -3395,7 +3393,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3(supports-color@10.2.2) @@ -3477,7 +3475,7 @@ snapshots: '@fastify/proxy-addr@5.1.0': dependencies: '@fastify/forwarded': 3.0.1 - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 '@fastify/send@4.1.0': dependencies: @@ -3487,14 +3485,14 @@ snapshots: http-errors: 2.0.1 mime: 3.0.0 - '@fastify/sensible@6.0.3': + '@fastify/sensible@6.0.4': dependencies: '@lukeed/ms': 2.0.2 dequal: 2.0.3 fastify-plugin: 5.1.0 forwarded: 0.2.0 http-errors: 2.0.1 - type-is: 1.6.18 + type-is: 2.0.1 vary: 1.1.2 '@fastify/static@8.3.0': @@ -3512,7 +3510,7 @@ snapshots: fastify-plugin: 5.1.0 openapi-types: 12.1.3 rfdc: 1.4.1 - yaml: 2.8.1 + yaml: 2.8.2 '@fastify/swagger@9.6.1': dependencies: @@ -3520,7 +3518,7 @@ snapshots: json-schema-resolver: 3.0.0 openapi-types: 12.1.3 rfdc: 1.4.1 - yaml: 2.8.1 + yaml: 2.8.2 transitivePeerDependencies: - supports-color @@ -3859,13 +3857,6 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@redocly/ajv@8.11.4': - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js-replace: 1.0.1 - '@redocly/ajv@8.17.1': dependencies: fast-deep-equal: 3.1.3 @@ -3873,14 +3864,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.12.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.12.0(ajv@8.17.1) - '@redocly/respect-core': 2.12.0(ajv@8.17.1) + '@redocly/openapi-core': 2.12.1(ajv@8.17.1) + '@redocly/respect-core': 2.12.1(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3931,11 +3922,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.12.0(ajv@8.17.1)': + '@redocly/openapi-core@2.12.1(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 '@redocly/config': 0.40.0 - ajv-formats: 2.1.1(ajv@8.17.1) + ajv-formats: 3.0.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 js-yaml: 4.1.1 @@ -3945,12 +3936,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.12.0(ajv@8.17.1)': + '@redocly/respect-core@2.12.1(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 - '@redocly/ajv': 8.11.4 - '@redocly/openapi-core': 2.12.0(ajv@8.17.1) + '@redocly/ajv': 8.17.1 + '@redocly/openapi-core': 2.12.1(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -4026,8 +4017,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@sinclair/typebox@0.34.41': {} - '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.0.0': {} @@ -4184,10 +4173,6 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@2.1.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -4446,6 +4431,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-type@1.0.5: {} + cookie@0.7.2: {} cookie@1.1.1: {} @@ -4656,7 +4643,7 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -4714,7 +4701,7 @@ snapshots: expand-template@2.0.3: {} - fast-copy@3.0.2: {} + fast-copy@4.0.0: {} fast-decode-uri-component@1.0.1: {} @@ -4769,7 +4756,7 @@ snapshots: generify: 4.2.0 help-me: 5.0.0 is-docker: 2.2.1 - pino-pretty: 13.1.2 + pino-pretty: 13.1.3 pkg-up: 3.1.0 resolve-from: 5.0.0 semver: 7.7.3 @@ -5058,7 +5045,7 @@ snapshots: ip-address@10.1.0: {} - ipaddr.js@2.2.0: {} + ipaddr.js@2.3.0: {} is-binary-path@2.1.0: dependencies: @@ -5179,7 +5166,7 @@ snapshots: nano-spawn: 2.0.0 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.8.1 + yaml: 2.8.2 listr2@9.0.5: dependencies: @@ -5224,7 +5211,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} lru-cache@7.18.3: {} @@ -5240,7 +5227,7 @@ snapshots: math-intrinsics@1.1.0: {} - media-typer@0.3.0: {} + media-typer@1.1.0: {} micromatch@4.0.8: dependencies: @@ -5249,10 +5236,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -5501,7 +5494,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 minipass: 7.1.2 path-to-regexp@8.3.0: {} @@ -5520,17 +5513,21 @@ snapshots: dependencies: split2: 4.2.0 - pino-pretty@13.1.2: + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: dependencies: colorette: 2.0.20 dateformat: 4.6.3 - fast-copy: 3.0.2 + fast-copy: 4.0.0 fast-safe-stringify: 2.1.1 help-me: 5.0.0 joycon: 3.1.1 minimist: 1.2.8 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 + pino-abstract-transport: 3.0.0 pump: 3.0.3 secure-json-parse: 4.1.0 sonic-boom: 4.2.0 @@ -6151,12 +6148,13 @@ snapshots: type-fest@4.41.0: {} - type-is@1.6.18: + type-is@2.0.1: dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 - typebox@1.0.56: {} + typebox@1.0.59: {} typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): dependencies: @@ -6190,8 +6188,6 @@ snapshots: unpipe@1.0.0: {} - uri-js-replace@1.0.1: {} - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6204,22 +6200,22 @@ snapshots: util-deprecate@1.0.2: {} - uuidv7@1.0.2: {} + uuidv7@1.1.0: {} vary@1.1.2: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)): dependencies: debug: 4.4.3(supports-color@10.2.2) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.4(@types/node@22.19.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.4(@types/node@22.19.1)(yaml@2.8.1): + vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6230,9 +6226,9 @@ snapshots: optionalDependencies: '@types/node': 22.19.1 fsevents: 2.3.3 - yaml: 2.8.1 + yaml: 2.8.2 - vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1): + vite@7.2.6(@types/node@24.10.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6243,7 +6239,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 fsevents: 2.3.3 - yaml: 2.8.1 + yaml: 2.8.2 walker@1.0.8: dependencies: @@ -6308,7 +6304,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.8.1: {} + yaml@2.8.2: {} yargs-parser@20.2.9: {} diff --git a/src/user/package.json b/src/user/package.json index 9efe221..896e36c 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -21,17 +21,17 @@ "@fastify/autoload": "^6.3.1", "@fastify/formbody": "^8.0.2", "@fastify/multipart": "^9.3.0", - "@fastify/sensible": "^6.0.3", + "@fastify/sensible": "^6.0.4", "@fastify/static": "^8.3.0", "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.56" + "typebox": "^1.0.59" }, "devDependencies": { "@types/node": "^22.19.1", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.4", + "vite": "^7.2.6", "vite-tsconfig-paths": "^5.1.4" } } From 26c9bd64214b5d26c0786768bfe3dc23e6fa4b20 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 2 Dec 2025 12:43:57 +0100 Subject: [PATCH 31/66] Trying to fix BUG with / when guest changes name without closing the window --- frontend/src/pages/chat/chat.ts | 49 +++++++++++++++++---------------- src/chat/src/app.ts | 20 ++++++++------ 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index f03088e..cff6c80 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -49,11 +49,12 @@ async function isLoggedIn() { async function windowStateHidden() { const socketId = __socket || undefined; - let oldName = localStorage.getItem("oldName") || undefined; - if (socketId == undefined) return; + // let oldName = localStorage.getItem("oldName") ?? undefined; + let oldName: string; + if (socketId === undefined) return; let userName = await updateUser(); - oldName = userName?.name || undefined; - if (oldName === undefined) return; + oldName = userName?.name ?? ""; + if (oldName === "") return; localStorage.setItem('oldName', oldName); socketId.emit('client_left', { user: userName?.name, @@ -66,11 +67,12 @@ async function windowStateHidden() { async function windowStateVisable() { const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; - console.log("%coldName :'" + oldName + "'", color.green); + console.log("%c WINDOW VISIBLE - oldName :'" + oldName + "'", color.green); if (socketId === undefined || oldName === undefined) {console.log("%SOCKET ID", color.red); return;} - const res = await client.guestLogin(); + // const res = await client.guestLogin(); let user = await updateUser(); + if(user === null) return; console.log("%cUserName :'" + user?.name + "'", color.green); socketId.emit('client_entered', { userName: oldName, @@ -184,21 +186,21 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn let toggle = false window.addEventListener("focus", () => { - if (window.location.pathname === "" && !toggle) { - // bconnected.click(); - console.log("%cWindow is focused on /chat:" + socket.id, color.green); - if (socket.id) - windowStateVisable(); - toggle = true; + const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; + if (window.location.pathname === '/app/chat') { + console.log("%cWindow is focused on /chat:" + socket.id, color.green); + if (socket.id) + windowStateVisable(); + bwhoami.click(); + toggle = true; } }); window.addEventListener("blur", () => { - //bconnected.click(); console.log("%cWindow is not focused on /chat", color.red); if (socket.id) windowStateHidden(); - toggle = false; + toggle = false; }); setTitle('Chat Page'); @@ -303,20 +305,23 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn bconnected?.addEventListener("click", async () => { const loggedIn = await isLoggedIn(); - let oldUser = localStorage.getItem("oldName") || undefined; - if (loggedIn?.name === undefined) return ; + console.log('%cloggedIn:',color.blue, loggedIn?.name); + let oldUser = localStorage.getItem("oldName") ?? ""; + console.log('%coldUser:',color.yellow, oldUser); + if (loggedIn?.name === undefined) {console.log('');return ;} oldUser = loggedIn.name || "undefined"; const res = await client.guestLogin(); let user = await updateUser(); + console.log('%User?name:',color.yellow, user?.name); localStorage.setItem("oldName", oldUser); buddies.textContent = ""; - if (chatWindow) { + // if (chatWindow) { // addMessage('@list - lists all connected users in the chat'); socket.emit('list', { oldUser: oldUser, user: user?.name, }); - } + // } }); @@ -337,18 +342,16 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn bwhoami?.addEventListener('click', async () => { try { const loggedIn = await isLoggedIn(); - let oldUser = localStorage.getItem("oldName") || undefined; - oldUser = loggedIn?.name || "undefined"; - localStorage.setItem("oldName", oldUser); const res = await client.guestLogin(); switch (res.kind) { case 'success': { let user = await updateUser(); - console.log('USER ', user?.name); + console.log('%cUSER_NAME ',color.yellow, user?.name); + console.log('%cGET_user ',color.yellow, getUser()?.name || null); if (chatWindow) { socket.emit('updateClientName', { - oldUser: oldUser, + oldUser: '', user: user?.name }); } diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index e426758..f5082b4 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -237,10 +237,10 @@ function broadcast(data: ClientMessage, sender?: string) { if (userFromFrontend.oldUser !== userFromFrontend.user) { console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset); - if (client?.user === null) { - console.log('ERROR: clientName is NULL'); - return; - }; + // if (client?.user === null) { + // console.log('ERROR: clientName is NULL'); + // return; + // }; if (client) { client.user = userFromFrontend.user; } @@ -254,12 +254,14 @@ function broadcast(data: ClientMessage, sender?: string) { console.log(color.red, 'whoAMi activated', userFromFrontend, color.reset, socket.id); if (userFromFrontend.oldUser !== userFromFrontend.user) { console.log(color.red, 'whoAMi activated', userFromFrontend.oldUser, color.reset); - if (client === null) { - console.log('ERROR: clientName is NULL'); - return; - }; + // if (client === null) { + // console.log('ERROR: clientName is NULL'); + // return; + // }; if (client) { client.user = userFromFrontend.user; + console.log(color.green, 'client.user is: ', client.user); + } } }); @@ -328,7 +330,7 @@ function broadcast(data: ClientMessage, sender?: string) { timestamp: Date.now(), SenderWindowID: socket.id, }; - console.log(obj.SenderWindowID); + console.log(color.blue, 'BROADCASTS OUT :',obj.SenderWindowID); broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } From 8a151cfb5e9cbbd80e8842d0adb777a2116d5720 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 2 Dec 2025 16:52:41 +0100 Subject: [PATCH 32/66] chat general broadcast done with now notifications system separated in different space on the chat windo --- frontend/src/@types/dom.d.ts | 2 ++ frontend/src/chat/chat.css | 28 ++++++++++++++-- frontend/src/pages/chat/chat.html | 10 +++--- frontend/src/pages/chat/chat.ts | 53 ++++++++++++++++++++----------- src/chat/src/app.ts | 18 +++++++---- src/chat/src/routes/nginx-chat.ts | 48 ---------------------------- 6 files changed, 81 insertions(+), 78 deletions(-) diff --git a/frontend/src/@types/dom.d.ts b/frontend/src/@types/dom.d.ts index 5cbf23c..1c35dd9 100644 --- a/frontend/src/@types/dom.d.ts +++ b/frontend/src/@types/dom.d.ts @@ -9,4 +9,6 @@ declare global { dispatchEvent(ev: CustomEventMap[K]): void; } } + + export { }; //keep that for TS compiler. diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 95c2bb5..eb43dce 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -40,7 +40,6 @@ active:shadow-[0_2px_0_0_black];; } - .chatbox-style { @apply w-[650px] @@ -60,12 +59,37 @@ mx-auto; } +.system-info { + @apply + h-[40px] + bg-gray-200 + text-gray-700 + p-3 + rounded-3xl + mb-2 border + border-gray-200 + text-center + shadow + overflow-y-auto + justify-end /* 👈 forces text to bottom */ + relative; /* needed for overlay */ +} + + +.text-info { + @apply + text-blue-800 + +} + + .chat-window-style { @apply w-[400px] h-[50px] p-[10px] - border-1 border-black + border-1 + border-black shadow-sm flex-1 rounded-3xl diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index e6d9e92..2882ae5 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -7,9 +7,10 @@ - - + +
System: connecting ...
+
@@ -20,7 +21,6 @@
-

Ping Buddies

@@ -34,7 +34,9 @@
-

From this Chat Box you can send messages to other players

+ + + diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index cff6c80..26dd177 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -4,17 +4,22 @@ import authHtml from './chat.html?raw'; import client from '@app/api' import { getUser, updateUser } from "@app/auth"; import io, { Socket } from 'socket.io-client'; +// import type { ClientMessage } from "@app/@types/dom"; const color = { - red: 'color: red; font-weight: bold;', - green: 'color: green; font-weight: bold;', - yellow: 'color: orange; font-weight: bold;', - blue: 'color: blue; font-weight: bold;', + red: 'color: red;', + green: 'color: green;', + yellow: 'color: orange;', + blue: 'color: blue;', reset: '', }; - - +export type ClientMessage = { + destination: string; + user: string; + text: string; + SenderWindowID: string; +}; // get the name of the machine used to connect @@ -143,26 +148,39 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); // Listen for messages from the server "MsgObjectServer" - socket.on("MsgObjectServer", (data: any) => { - console.log("Message Obj Recieved:", data.message); - console.log("Recieved data.message.text: ", data.message.text); - console.log("Recieved data.message.user: ", data.message.user); - console.log("Recieved data.message.type: ", data.message.type); - console.log("Recieved data.message.token: ", data.message.token); - console.log("Recieved data.message.timestamp: ", data.message.timestamp); + socket.on("MsgObjectServer", (data: { message: ClientMessage}) => { + console.log("Message Obj Recieved:", data); + console.log("%cRecieved data.message.text: ", color.blue, data.message.text); + console.log("%cRecieved data.message.user: ", color.blue, data.message.user); // Display the message in the chat window + const systemWindow = document.getElementById('system-box') as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; const bconnected = document.getElementById('b-help') as HTMLButtonElement; if (bconnected) { bconnected.click(); } - if (chatWindow) { + if (chatWindow && data.message.destination === "") { const messageElement = document.createElement("div"); messageElement.textContent = `${data.message.user}: ${data.message.text}`; chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; } + const MAX_SYSTEM_MESSAGES = 10; + + if (systemWindow && data.message.destination === "system-info") { + const messageElement = document.createElement("div"); + messageElement.textContent = `${data.message.user}: ${data.message.text}`; + systemWindow.appendChild(messageElement); + + // keep only last 10 + while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) { + systemWindow.removeChild(systemWindow.firstChild!); + } + + systemWindow.scrollTop = systemWindow.scrollHeight; + } + console.log("Getuser():", getUser()); }); @@ -261,6 +279,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const user = getUser(); if (user && socket?.connected) { const message = { + destination: "", type: "chat", user: user.name, token: document.cookie, @@ -298,7 +317,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn setInterval(async () => { - bconnected.click(); + //bconnected.click(); }, 10000); // every 10 second // Help Text button @@ -312,7 +331,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn oldUser = loggedIn.name || "undefined"; const res = await client.guestLogin(); let user = await updateUser(); - console.log('%User?name:',color.yellow, user?.name); + console.log('%cUser?name:',color.yellow, user?.name); localStorage.setItem("oldName", oldUser); buddies.textContent = ""; // if (chatWindow) { @@ -347,8 +366,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn switch (res.kind) { case 'success': { let user = await updateUser(); - console.log('%cUSER_NAME ',color.yellow, user?.name); - console.log('%cGET_user ',color.yellow, getUser()?.name || null); if (chatWindow) { socket.emit('updateClientName', { oldUser: '', diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index f5082b4..933048b 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -32,6 +32,13 @@ interface ClientInfo { lastSeen: number; } +export type ClientMessage = { + destination: string; + user: string; + text: string; + SenderWindowID: string; +}; + const clientChat = new Map(); // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... @@ -69,18 +76,13 @@ export default app; export { app }; -type ClientMessage = { - user: string; - text: string; - SenderWindowID: string; -}; // When using .decorate you have to specify added properties for Typescript declare module 'fastify' { interface FastifyInstance { io: Server<{ hello: (message: string) => string; - MsgObjectServer: (data: { message: ClientMessage }) => void; + MsgObjectServer: (data: { message: ClientMessage } ) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -273,6 +275,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (!clientName) return; console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); const obj = { + destination: "system-info", type: "chat" as const, user: clientName, token: "", @@ -300,6 +303,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (clientName !== null) { const obj = { + destination: "system-info", type: 'chat', user: clientName, token: '', @@ -323,6 +327,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (clientName !== null) { const obj = { + destination: "system-info", type: 'chat', user: clientName, token: '', @@ -367,6 +372,7 @@ function broadcast(data: ClientMessage, sender?: string) { ); if (clientName !== null) { const obj = { + destination: "system-info", type: 'chat', user: clientName, frontendUserName: userNameFromFrontend, diff --git a/src/chat/src/routes/nginx-chat.ts b/src/chat/src/routes/nginx-chat.ts index e6dd4df..99dc7a6 100644 --- a/src/chat/src/routes/nginx-chat.ts +++ b/src/chat/src/routes/nginx-chat.ts @@ -34,54 +34,6 @@ export const ChatRes = { export type ChatResType = MakeStaticResponse; -function connectedUser(io: Server | undefined, targetSocketId?: string): number { - let count = 0; - - // Track unique usernames (avoid duplicates) - const seenUsers = new Set(); - - for (const [socketId, info] of clientChat) { - - // Validate entry - if (!info || typeof info.user !== "string" || info.user.trim() === "") { - clientChat.delete(socketId); - continue; - } - - const username = info.user; - - // Validate socket exists if io is passed - if (io) { - const socket = io.sockets.sockets.get(socketId); - - // Remove disconnected sockets - if (!socket || socket.disconnected) { - clientChat.delete(socketId); - continue; - } - } - - // Skip duplicates - if (seenUsers.has(username)) - continue; - - seenUsers.add(username); - count++; - - // Send to target only - if (io && targetSocketId) { - io.to(targetSocketId).emit("listBud", username); - } - - console.log(color.yellow, "Client:", color.reset, username); - console.log(color.yellow, "Socket ID:", color.reset, socketId); - } - - return count; -} - - - const route: FastifyPluginAsync = async (fastify): Promise => { fastify.get( From bde038a8d01e6ac61293337a540fe0647d7418fe Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 2 Dec 2025 17:11:50 +0100 Subject: [PATCH 33/66] added eslint norme to code --- src/@shared/package.json | 2 +- src/@shared/src/database/mixin/blocked.ts | 4 +- src/auth/package.json | 2 +- src/chat/package.json | 2 +- src/chat/src/app.ts | 75 +++++----- src/chat/src/routes/nginx-chat.ts | 34 ++--- src/package.json | 6 +- src/pnpm-lock.yaml | 158 +++++++++++----------- src/user/package.json | 2 +- 9 files changed, 128 insertions(+), 157 deletions(-) diff --git a/src/@shared/package.json b/src/@shared/package.json index 15f2935..d563f63 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -20,7 +20,7 @@ "fastify-plugin": "^5.1.0", "joi": "^18.0.2", "otp": "^1.1.2", - "typebox": "^1.0.59", + "typebox": "^1.0.61", "uuidv7": "^1.1.0" }, "devDependencies": { diff --git a/src/@shared/src/database/mixin/blocked.ts b/src/@shared/src/database/mixin/blocked.ts index 13db21d..a6b0a28 100644 --- a/src/@shared/src/database/mixin/blocked.ts +++ b/src/@shared/src/database/mixin/blocked.ts @@ -30,11 +30,10 @@ export const BlockedImpl: Omit = { this.prepare('DELETE FROM blocked WHERE user = @id AND blocked = @blocked').run({ id, blocked }); }, - /** * Get all blocked user * - * @param + * @param * * @returns The list of users if it exists, undefined otherwise */ @@ -64,7 +63,6 @@ export type BlockedData = { * @returns The blocked if it exists, undefined otherwise */ export function blockedFromRow(row?: Partial): BlockedData | undefined { - console.log('HELLO ?????', row); if (isNullish(row)) return undefined; if (isNullish(row.id)) return undefined; if (isNullish(row.user)) return undefined; diff --git a/src/auth/package.json b/src/auth/package.json index 9c9dd49..81d3afe 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.59" + "typebox": "^1.0.61" }, "devDependencies": { "@types/node": "^22.19.1", diff --git a/src/chat/package.json b/src/chat/package.json index 56396ad..1e61398 100644 --- a/src/chat/package.json +++ b/src/chat/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-plugin": "^5.1.0", "socket.io": "^4.8.1", - "typebox": "^1.0.59" + "typebox": "^1.0.61" }, "devDependencies": { "@types/node": "^22.19.1", diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 933048b..5d30955 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -75,14 +75,12 @@ const app: FastifyPluginAsync = async (fastify, opts): Promise => { export default app; export { app }; - - // When using .decorate you have to specify added properties for Typescript declare module 'fastify' { interface FastifyInstance { io: Server<{ hello: (message: string) => string; - MsgObjectServer: (data: { message: ClientMessage } ) => void; + MsgObjectServer: (data: { message: ClientMessage }) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -161,32 +159,27 @@ async function onReady(fastify: FastifyInstance) { return count; } -function broadcast(data: ClientMessage, sender?: string) { - fastify.io.fetchSockets().then((sockets) => { - for (const s of sockets) { - - // Skip sender's own socket - if (s.id === sender) continue; - - // Get client name from map - const clientInfo = clientChat.get(s.id); - - if (!clientInfo?.user) { - console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); - continue; + function broadcast(data: ClientMessage, sender?: string) { + fastify.io.fetchSockets().then((sockets) => { + for (const s of sockets) { + // Skip sender's own socket + if (s.id === sender) continue; + // Get client name from map + const clientInfo = clientChat.get(s.id); + if (!clientInfo?.user) { + console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); + continue; + } + // Emit structured JSON object + s.emit('MsgObjectServer', { message: data }); + // Debug logs + console.log(color.green, 'Broadcast to:', clientInfo.user); + console.log(' Target socket ID:', s.id); + console.log(' Target rooms:', [...s.rooms]); + console.log(' Sender socket ID:', sender ?? 'none'); } - - // Emit structured JSON object - s.emit("MsgObjectServer", { message: data }); - - // Debug logs - console.log(color.green, "Broadcast to:", clientInfo.user); - console.log(" Target socket ID:", s.id); - console.log(" Target rooms:", [...s.rooms]); - console.log(" Sender socket ID:", sender ?? "none"); - } - }); -} + }); + } fastify.io.on('connection', (socket: Socket) => { @@ -239,7 +232,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (userFromFrontend.oldUser !== userFromFrontend.user) { console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset); - // if (client?.user === null) { + // if (client?.user === null) { // console.log('ERROR: clientName is NULL'); // return; // }; @@ -268,18 +261,18 @@ function broadcast(data: ClientMessage, sender?: string) { } }); - socket.on("logout", () => { + socket.on('logout', () => { const clientInfo = clientChat.get(socket.id); const clientName = clientInfo?.user; - + if (!clientName) return; console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); const obj = { - destination: "system-info", - type: "chat" as const, + destination: 'system-info', + type: 'chat' as const, user: clientName, - token: "", - text: "LEFT the chat", + token: '', + text: 'LEFT the chat', timestamp: Date.now(), SenderWindowID: socket.id, }; @@ -287,11 +280,9 @@ function broadcast(data: ClientMessage, sender?: string) { // Optional: remove from map clientChat.delete(socket.id); // Ensure socket is fully disconnected - if (socket.connected) socket.disconnect(true); + if (socket.connected) socket.disconnect(true); }); - - socket.on('disconnecting', (reason) => { const clientName = clientChat.get(socket.id)?.user || null; console.log( @@ -303,7 +294,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (clientName !== null) { const obj = { - destination: "system-info", + destination: 'system-info', type: 'chat', user: clientName, token: '', @@ -327,7 +318,7 @@ function broadcast(data: ClientMessage, sender?: string) { if (clientName !== null) { const obj = { - destination: "system-info", + destination: 'system-info', type: 'chat', user: clientName, token: '', @@ -335,7 +326,7 @@ function broadcast(data: ClientMessage, sender?: string) { timestamp: Date.now(), SenderWindowID: socket.id, }; - console.log(color.blue, 'BROADCASTS OUT :',obj.SenderWindowID); + console.log(color.blue, 'BROADCASTS OUT :', obj.SenderWindowID); broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } @@ -372,7 +363,7 @@ function broadcast(data: ClientMessage, sender?: string) { ); if (clientName !== null) { const obj = { - destination: "system-info", + destination: 'system-info', type: 'chat', user: clientName, frontendUserName: userNameFromFrontend, diff --git a/src/chat/src/routes/nginx-chat.ts b/src/chat/src/routes/nginx-chat.ts index 99dc7a6..1ba77c2 100644 --- a/src/chat/src/routes/nginx-chat.ts +++ b/src/chat/src/routes/nginx-chat.ts @@ -1,8 +1,8 @@ import { FastifyPluginAsync } from 'fastify'; import { MakeStaticResponse, typeResponse } from '@shared/utils'; import { Type } from 'typebox'; -import { UserId } from '@shared/database/mixin/user'; -import { Server } from 'socket.io'; +// import { UserId } from '@shared/database/mixin/user'; +// import { Server } from 'socket.io'; // colors for console.log export const color = { @@ -13,16 +13,6 @@ export const color = { reset: '\x1b[0m', }; -// Global map of clients -// key = socket, value = clientname -interface ClientInfo { - user: string; - lastSeen: number; -} - -const clientChat = new Map(); - - export const ChatRes = { 200: typeResponse('success', 'chat.success', { name: Type.String(), @@ -33,8 +23,6 @@ export const ChatRes = { export type ChatResType = MakeStaticResponse; - - const route: FastifyPluginAsync = async (fastify): Promise => { fastify.get( '/api/chat/test', @@ -47,23 +35,17 @@ const route: FastifyPluginAsync = async (fastify): Promise => { }, async (req, res) => { - - - let users = fastify.db.getAllUsers(); - console.log("ALL USERS EVER CONNECTED:", users); - + const users = fastify.db.getAllUsers(); + console.log('ALL USERS EVER CONNECTED:', users); if (!users) return; for (const user of users) { - console.log(color.yellow, "USER:", user.name); + console.log(color.yellow, 'USER:', user.name); } - // const usersBlocked = fastify.db.getAllBlockedUsers(); // console.log(color.red, "ALL BLOCKED USERS:", usersBlocked); - fastify.db.addBlockedUserFor(users[0].id, users[1].id) - let usersBlocked2; - usersBlocked2 = fastify.db.getAllBlockedUsers(); - console.log(color.green, "ALL BLOCKED USERS:", usersBlocked2); - + fastify.db.addBlockedUserFor(users[0].id, users[1].id); + const usersBlocked2 = fastify.db.getAllBlockedUsers(); + console.log(color.green, 'ALL BLOCKED USERS:', usersBlocked2); res.makeResponse(200, 'success', 'CCChat.success', { name: 'name', 'id': req.authUser!.id, guest: false }); }, ); diff --git a/src/package.json b/src/package.json index 4858a48..9fa1e9c 100644 --- a/src/package.json +++ b/src/package.json @@ -24,15 +24,15 @@ "devDependencies": { "@eslint/js": "^9.39.1", "@openapitools/openapi-generator-cli": "^2.25.2", - "@typescript-eslint/eslint-plugin": "^8.48.0", - "@typescript-eslint/parser": "^8.48.0", + "@typescript-eslint/eslint-plugin": "^8.48.1", + "@typescript-eslint/parser": "^8.48.1", "eslint": "^9.39.1", "husky": "^9.1.7", "lint-staged": "^16.2.7", "openapi-generator-cli": "^1.0.0", "openapi-typescript": "^7.10.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.48.0", + "typescript-eslint": "^8.48.1", "vite": "^7.2.6" }, "dependencies": { diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index a7690b6..ad74292 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -22,11 +22,11 @@ importers: specifier: ^2.25.2 version: 2.25.2(@types/node@24.10.1) '@typescript-eslint/eslint-plugin': - specifier: ^8.48.0 - version: 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.48.0 - version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) eslint: specifier: ^9.39.1 version: 9.39.1 @@ -46,8 +46,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.48.0 - version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) vite: specifier: ^7.2.6 version: 7.2.6(@types/node@24.10.1)(yaml@2.8.2) @@ -88,8 +88,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 typebox: - specifier: ^1.0.59 - version: 1.0.59 + specifier: ^1.0.61 + version: 1.0.61 uuidv7: specifier: ^1.1.0 version: 1.1.0 @@ -131,8 +131,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.59 - version: 1.0.59 + specifier: ^1.0.61 + version: 1.0.61 devDependencies: '@types/node': specifier: ^22.19.1 @@ -177,8 +177,8 @@ importers: specifier: ^4.8.1 version: 4.8.1 typebox: - specifier: ^1.0.59 - version: 1.0.59 + specifier: ^1.0.61 + version: 1.0.61 devDependencies: '@types/node': specifier: ^22.19.1 @@ -266,8 +266,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.59 - version: 1.0.59 + specifier: ^1.0.61 + version: 1.0.61 devDependencies: '@types/node': specifier: ^22.19.1 @@ -1119,63 +1119,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.48.0': - resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + '@typescript-eslint/eslint-plugin@8.48.1': + resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.0 + '@typescript-eslint/parser': ^8.48.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.0': - resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + '@typescript-eslint/parser@8.48.1': + resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.0': - resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.0': - resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.0': - resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.0': - resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + '@typescript-eslint/type-utils@8.48.1': + resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.0': - resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.0': - resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.0': - resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.0': - resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} abort-controller@3.0.0: @@ -3026,11 +3026,11 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typebox@1.0.59: - resolution: {integrity: sha512-D8pTcn4yPzUb34OWFyPVITP1fMZjJXV5n7tT5ss/BxHSnzfuMK4rQEFunpk1UOwq9lMCOed2BJ3GXGoIBKl7Rw==} + typebox@1.0.61: + resolution: {integrity: sha512-5KeeL5QoPBoYm8Z7tGR1Pw9FjWA75MLhVuiSMCRgtgTg/d2+kTvolFddhOUua9FxpIaqXznFPZcc3sl6cEpafw==} - typescript-eslint@8.48.0: - resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + typescript-eslint@8.48.1: + resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -4062,14 +4062,14 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 eslint: 9.39.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -4079,41 +4079,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.0': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 - '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -4121,14 +4121,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.0': {} + '@typescript-eslint/types@8.48.1': {} - '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 @@ -4138,20 +4138,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.0': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 abort-controller@3.0.0: @@ -6154,14 +6154,14 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typebox@1.0.59: {} + typebox@1.0.61: {} - typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.48.1(eslint@9.39.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: diff --git a/src/user/package.json b/src/user/package.json index 896e36c..6dfb965 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -26,7 +26,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.59" + "typebox": "^1.0.61" }, "devDependencies": { "@types/node": "^22.19.1", From 1a8601e634d7866c61a5ab0c9e7eb2b3201124af Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 3 Dec 2025 21:52:45 +0100 Subject: [PATCH 34/66] minor bug fixed SESSION_MANAGER when not in env --- src/chat/src/app.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 5d30955..4ac2c96 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -18,10 +18,11 @@ export const color = { // shows address for connection au server transcendance const session = process.env.SESSION_MANAGER ?? ''; -const part = session.split('/')[1]; -const machineName = part.split('.')[0]; -console.log(color.yellow, 'Connect at : https://' + machineName + ':8888/app/login'); - +if (session) { + const part = session.split('/')[1]; + const machineName = part.split('.')[0]; + console.log(color.yellow, 'Connect at : https://' + machineName + ':8888/app/login'); +} declare const __SERVICE_NAME: string; From b441cc3f3c3948c6e8dd7ca5263c7f81eebeef01 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 4 Dec 2025 10:01:28 +0100 Subject: [PATCH 35/66] commented out experience with database can block the chat at the moment --- src/chat/src/routes/nginx-chat.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/chat/src/routes/nginx-chat.ts b/src/chat/src/routes/nginx-chat.ts index 1ba77c2..02162cc 100644 --- a/src/chat/src/routes/nginx-chat.ts +++ b/src/chat/src/routes/nginx-chat.ts @@ -35,17 +35,17 @@ const route: FastifyPluginAsync = async (fastify): Promise => { }, async (req, res) => { - const users = fastify.db.getAllUsers(); - console.log('ALL USERS EVER CONNECTED:', users); - if (!users) return; - for (const user of users) { - console.log(color.yellow, 'USER:', user.name); - } - // const usersBlocked = fastify.db.getAllBlockedUsers(); - // console.log(color.red, "ALL BLOCKED USERS:", usersBlocked); - fastify.db.addBlockedUserFor(users[0].id, users[1].id); - const usersBlocked2 = fastify.db.getAllBlockedUsers(); - console.log(color.green, 'ALL BLOCKED USERS:', usersBlocked2); + // const users = fastify.db.getAllUsers(); + // console.log('ALL USERS EVER CONNECTED:', users); + // if (!users) return; + // for (const user of users) { + // console.log(color.yellow, 'USER:', user.name); + // } + // // const usersBlocked = fastify.db.getAllBlockedUsers(); + // // console.log(color.red, "ALL BLOCKED USERS:", usersBlocked); + // fastify.db.addBlockedUserFor(users[0].id, users[1].id); + // const usersBlocked2 = fastify.db.getAllBlockedUsers(); + // console.log(color.green, 'ALL BLOCKED USERS:', usersBlocked2); res.makeResponse(200, 'success', 'CCChat.success', { name: 'name', 'id': req.authUser!.id, guest: false }); }, ); From 9e262c7fc4a7ff55216422dd3cc7cf789223299f Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 4 Dec 2025 11:09:47 +0100 Subject: [PATCH 36/66] arrival messaeg in system info working --- frontend/src/pages/chat/chat.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 26dd177..02f2816 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -35,8 +35,8 @@ document.addEventListener('ft:pageChange', () => { }) function getSocket(): Socket { - // let addressHost = `wss://${machineHostName}:8888`; - let addressHost = `wss://localhost:8888`; + let addressHost = `wss://${machineHostName}:8888`; + // let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -137,6 +137,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Ensure we have a user AND socket is connected if (!user || !socket.connected) return; const message = { + destination: 'system-info', type: "chat", user, token: document.cookie ?? "", From b1bf460ef206cc3357bcc60164a6449155fcb856 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 4 Dec 2025 18:04:41 +0100 Subject: [PATCH 37/66] made bconnect.click() into function, seems to have solved the re loading of ping buddies after guest name change --- frontend/src/pages/chat/chat.ts | 239 ++++++++++++++++++++------------ src/chat/src/app.ts | 5 + 2 files changed, 153 insertions(+), 91 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 02f2816..4bfb307 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -4,7 +4,6 @@ import authHtml from './chat.html?raw'; import client from '@app/api' import { getUser, updateUser } from "@app/auth"; import io, { Socket } from 'socket.io-client'; -// import type { ClientMessage } from "@app/@types/dom"; const color = { red: 'color: red;', @@ -15,6 +14,7 @@ const color = { }; export type ClientMessage = { + command: string destination: string; user: string; text: string; @@ -32,7 +32,7 @@ document.addEventListener('ft:pageChange', () => { __socket.close(); __socket = undefined; console.log("Page changed"); -}) +}); function getSocket(): Socket { let addressHost = `wss://${machineHostName}:8888`; @@ -45,12 +45,24 @@ function getSocket(): Socket { transports: ["websocket"], }); return __socket; -} +}; + + +function addMessage(text: string) { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (!chatWindow) return; + const messageElement = document.createElement("div-test"); + messageElement.textContent = text; + chatWindow.appendChild(messageElement); + chatWindow.scrollTop = chatWindow.scrollHeight; + console.log(`Added new message: ${text}`) + return ; +}; async function isLoggedIn() { return getUser() || null; -} +}; async function windowStateHidden() { const socketId = __socket || undefined; @@ -66,7 +78,7 @@ async function windowStateHidden() { why: 'tab window hidden - socket not dead', }); return; -} +}; async function windowStateVisable() { @@ -85,8 +97,30 @@ async function windowStateVisable() { }); setTitle('Chat Page'); return; -} +}; +function parseCmdMsg(msgText: string): string[] | undefined { + if (!msgText?.trim()) { + console.log('%c DEBUG - in FN parseCmdMsg : msgText = ""', color.red); + return; + } + msgText = msgText.trim(); + // Find the first space + const firstSpaceIndex = msgText.indexOf(' '); + const cmd = firstSpaceIndex === -1 ? msgText : msgText.slice(0, firstSpaceIndex); + const rest = firstSpaceIndex === -1 ? "" : msgText.slice(firstSpaceIndex + 1).trim(); + const command: string[] = ["", ""]; + if (!msgText.startsWith('@')) { + command[0] = "@msg"; + command[1] = msgText; + } else { + command[0] = cmd; + command[1] = rest; + } + console.log('%c DEBUG - split msgText[0]:', color.red, command[0]); + console.log('%c DEBUG - split msgText[1]:', color.red, command[1]); + return command; +}; async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { @@ -98,14 +132,15 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { console.log(`Added buddies: ${listBuddies}`) return ; -} +}; function waitSocketConnected(socket: Socket): Promise { return new Promise(resolve => { if (socket.connected) return resolve(); // already connected socket.on("connect", () => resolve()); }); -} +}; + const bconnected = document.getElementById('b-help') as HTMLButtonElement; if (bconnected) { bconnected.click(); @@ -118,8 +153,52 @@ function logout(socket: Socket) { if (__socket !== undefined) __socket.close(); // window.location.href = "/login"; -} +}; +function broadcastMsg (socket: Socket, msgCommand: string[]): void { + let msgText = msgCommand[1] ?? ""; + console.log('%cmsgText:', color.red, msgText); + addMessage(msgText); + const user = getUser(); + if (user && socket?.connected) { + const message = { + command: msgCommand, + destination: "", + type: "chat", + user: user.name, + token: document.cookie, + text: msgText, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + socket.emit('message', JSON.stringify(message)); + } +}; + + +async function connected(socket: Socket): Promise { + + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const loggedIn = await isLoggedIn(); + console.log('%cloggedIn:',color.blue, loggedIn?.name); + let oldUser = localStorage.getItem("oldName") ?? ""; + console.log('%coldUser:',color.yellow, oldUser); + if (loggedIn?.name === undefined) {console.log('');return ;} + oldUser = loggedIn.name ?? ""; + const res = await client.guestLogin(); + let user = await updateUser(); + console.log('%cUser?name:',color.yellow, user?.name); + localStorage.setItem("oldName", oldUser); + buddies.textContent = ""; + // if (chatWindow) { + // addMessage('@list - lists all connected users in the chat'); + socket.emit('list', { + oldUser: oldUser, + user: user?.name, + }); + // } + +}; function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -130,13 +209,15 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Listen for the 'connect' event socket.on("connect", async () => { - + + const systemWindow = document.getElementById('system-box') as HTMLDivElement; await waitSocketConnected(socket); console.log("I AM Connected to the server:", socket.id); const user = getUser()?.name; // Ensure we have a user AND socket is connected if (!user || !socket.connected) return; const message = { + command: "", destination: 'system-info', type: "chat", user, @@ -146,6 +227,12 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn SenderWindowID: socket.id, }; socket.emit('message', JSON.stringify(message)); + // if (systemWindow) { + const messageElement = document.createElement("div"); + messageElement.textContent = `${user}: is connected au server`; + systemWindow.appendChild(messageElement); + systemWindow.scrollTop = systemWindow.scrollHeight; + // } }); // Listen for messages from the server "MsgObjectServer" @@ -157,8 +244,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const systemWindow = document.getElementById('system-box') as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; const bconnected = document.getElementById('b-help') as HTMLButtonElement; + if (bconnected) { - bconnected.click(); + connected(socket); } if (chatWindow && data.message.destination === "") { @@ -178,16 +266,12 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) { systemWindow.removeChild(systemWindow.firstChild!); } - systemWindow.scrollTop = systemWindow.scrollHeight; } - console.log("Getuser():", getUser()); }); - - socket.on('logout', () => { const bquit = document.getElementById('b-quit') as HTMLDivElement | null; if (bquit instanceof HTMLDivElement) { @@ -202,25 +286,24 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn color?: { default: string, hover: string }, }; - - let toggle = false - window.addEventListener("focus", () => { - const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; - if (window.location.pathname === '/app/chat') { - console.log("%cWindow is focused on /chat:" + socket.id, color.green); - if (socket.id) - windowStateVisable(); - bwhoami.click(); - toggle = true; + let toggle = false + window.addEventListener("focus", () => { + const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; + if (window.location.pathname === '/app/chat') { + console.log("%cWindow is focused on /chat:" + socket.id, color.green); + if (socket.id) { + windowStateVisable(); + connected(socket); } - }); - - window.addEventListener("blur", () => { - console.log("%cWindow is not focused on /chat", color.red); - if (socket.id) - windowStateHidden(); - toggle = false; - }); + toggle = true; + } + }); + window.addEventListener("blur", () => { + console.log("%cWindow is not focused on /chat", color.red); + if (socket.id) + windowStateHidden(); + toggle = false; + }); setTitle('Chat Page'); // Listen for the 'connect' event @@ -236,6 +319,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const username = document.getElementById('username') as HTMLDivElement; const buddies = document.getElementById('div-buddies') as HTMLDivElement; const bquit = document.getElementById('b-quit') as HTMLDivElement; + const systemWindow = document.getElementById('system-box') as HTMLDivElement; chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -252,56 +336,51 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log('unknown response: ', value); } - const addMessage = (text: string) => { - if (!chatWindow) return; - const messageElement = document.createElement("div-test"); - messageElement.textContent = text; - chatWindow.appendChild(messageElement); - chatWindow.scrollTop = chatWindow.scrollHeight; - console.log(`Added new message: ${text}`) - return ; - }; socket.once('welcome', (data) => { chatWindow.textContent = ''; chatWindow.innerHTML = ''; buddies.textContent = ''; buddies.innerHTML = ''; - bconnected.click(); + connected(socket); addMessage (`${data.msg} ` + getUser()?.name); - }); + }); // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { - const msgText = sendtextbox.value.trim(); - bconnected.click(); - addMessage(msgText); - const user = getUser(); - if (user && socket?.connected) { - const message = { - destination: "", - type: "chat", - user: user.name, - token: document.cookie, - text: msgText, - timestamp: Date.now(), - SenderWindowID: socket.id, - }; - socket.emit('message', JSON.stringify(message)); + let msgText: string = sendtextbox.value.trim(); + const msgCommand = parseCmdMsg(msgText) ?? ""; + connected(socket); + if (msgCommand !== "") { + switch (msgCommand[0]) { + case '@msg': + broadcastMsg(socket, msgCommand); + break; + case '@who': + bwhoami.click(); + break; + case '@cls': + clearText.click(); + break; + case '@quit': + bquit.click(); + break; + default: + addMessage('Command not known'); + break; + } + // Clear the input in all cases + sendtextbox.value = ""; } - sendtextbox.value = ""; } }); - - - // Clear Text button clearText?.addEventListener("click", () => { if (chatWindow) { - bconnected.click(); + connected(socket); chatWindow.innerHTML = ''; } }); @@ -310,7 +389,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (socket) { logout(socket); setTitle('Chat Page'); - bconnected.click(); + systemWindow.innerHTML = ""; + chatWindow.textContent = ""; + connected(socket); } else { getSocket(); } @@ -318,32 +399,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn setInterval(async () => { - //bconnected.click(); - }, 10000); // every 10 second - - // Help Text button - bconnected?.addEventListener("click", async () => { - - const loggedIn = await isLoggedIn(); - console.log('%cloggedIn:',color.blue, loggedIn?.name); - let oldUser = localStorage.getItem("oldName") ?? ""; - console.log('%coldUser:',color.yellow, oldUser); - if (loggedIn?.name === undefined) {console.log('');return ;} - oldUser = loggedIn.name || "undefined"; - const res = await client.guestLogin(); - let user = await updateUser(); - console.log('%cUser?name:',color.yellow, user?.name); - localStorage.setItem("oldName", oldUser); - buddies.textContent = ""; - // if (chatWindow) { - // addMessage('@list - lists all connected users in the chat'); - socket.emit('list', { - oldUser: oldUser, - user: user?.name, - }); - // } - - }); + //connected(socket); + },10000); // every 10 second socket.on('listBud', (myBuddies: string) => { console.log('List buddies connected ', myBuddies); diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 4ac2c96..733ba7f 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -34,6 +34,7 @@ interface ClientInfo { } export type ClientMessage = { + command: string destination: string; user: string; text: string; @@ -269,6 +270,7 @@ async function onReady(fastify: FastifyInstance) { if (!clientName) return; console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); const obj = { + command: '', destination: 'system-info', type: 'chat' as const, user: clientName, @@ -295,6 +297,7 @@ async function onReady(fastify: FastifyInstance) { if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, @@ -319,6 +322,7 @@ async function onReady(fastify: FastifyInstance) { if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, @@ -364,6 +368,7 @@ async function onReady(fastify: FastifyInstance) { ); if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, From bf66c11356c084352ff151f38f4bd9ab19b47a22 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Thu, 4 Dec 2025 18:58:23 +0100 Subject: [PATCH 38/66] end of the day --- frontend/src/pages/chat/chat.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 4bfb307..62659b4 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -361,7 +361,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn bwhoami.click(); break; case '@cls': - clearText.click(); + chatWindow.innerHTML = ''; break; case '@quit': bquit.click(); @@ -380,7 +380,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn clearText?.addEventListener("click", () => { if (chatWindow) { - connected(socket); chatWindow.innerHTML = ''; } }); From eb613c10f4fb4eb118b49609d327fdda3435aa9e Mon Sep 17 00:00:00 2001 From: NigeParis Date: Fri, 5 Dec 2025 16:37:26 +0100 Subject: [PATCH 39/66] First private messages working --- frontend/src/chat/chat.css | 13 +- frontend/src/pages/chat/chat.ts | 303 +++++++++++++++++++------------- src/chat/src/app.ts | 56 ++++++ src/package.json | 2 +- src/pnpm-lock.yaml | 101 ++++++----- 5 files changed, 303 insertions(+), 172 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index eb43dce..9c3bccd 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -159,8 +159,11 @@ div-buddies-list { @apply text-black - whitespace-pre-wrap; - + whitespace-pre-wrap + cursor-pointer + hover:text-blue-500 + transition-colors + duration-150; } p { @@ -180,4 +183,10 @@ div-notlog { text-red-800 text-3xl text-center; +} + +div-private { + @apply + text-blue-800; + } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 62659b4..8d380f1 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -47,7 +47,6 @@ function getSocket(): Socket { return __socket; }; - function addMessage(text: string) { const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; if (!chatWindow) return; @@ -59,7 +58,6 @@ function addMessage(text: string) { return ; }; - async function isLoggedIn() { return getUser() || null; }; @@ -80,14 +78,12 @@ async function windowStateHidden() { return; }; - async function windowStateVisable() { const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; console.log("%c WINDOW VISIBLE - oldName :'" + oldName + "'", color.green); if (socketId === undefined || oldName === undefined) {console.log("%SOCKET ID", color.red); return;} - // const res = await client.guestLogin(); let user = await updateUser(); if(user === null) return; console.log("%cUserName :'" + user?.name + "'", color.green); @@ -100,39 +96,57 @@ async function windowStateVisable() { }; function parseCmdMsg(msgText: string): string[] | undefined { - if (!msgText?.trim()) { - console.log('%c DEBUG - in FN parseCmdMsg : msgText = ""', color.red); - return; - } - msgText = msgText.trim(); - // Find the first space - const firstSpaceIndex = msgText.indexOf(' '); - const cmd = firstSpaceIndex === -1 ? msgText : msgText.slice(0, firstSpaceIndex); - const rest = firstSpaceIndex === -1 ? "" : msgText.slice(firstSpaceIndex + 1).trim(); - const command: string[] = ["", ""]; - if (!msgText.startsWith('@')) { - command[0] = "@msg"; - command[1] = msgText; - } else { - command[0] = cmd; - command[1] = rest; - } - console.log('%c DEBUG - split msgText[0]:', color.red, command[0]); - console.log('%c DEBUG - split msgText[1]:', color.red, command[1]); - return command; -}; -async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { + if (!msgText?.trim()) return; + msgText = msgText.trim(); + const command: string[] = ['', '']; + if (!msgText.startsWith('@')) { + command[0] = '@msg'; + command[1] = msgText; + return command; + } + const noArgCommands = ['@quit', '@cls', '@profile']; + if (noArgCommands.includes(msgText)) { + command[0] = msgText; + command[1] = ''; + return command; + } + const colonIndex = msgText.indexOf(":"); + if (colonIndex === -1) { + command[0] = msgText; + command[1] = ''; + return command; + } + const cmd = msgText.slice(0, colonIndex).trim(); + const rest = msgText.slice(colonIndex + 1).trim(); + command[0] = cmd; + command[1] = rest; + return command; +} + +async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { if (!buddies) return; + const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement; + const messageElement = document.createElement("div-buddies-list"); messageElement.textContent = listBuddies + '\n'; + + + // ✔ Add click-to-copy + messageElement.style.cursor = "pointer"; // optional visual hint + messageElement.addEventListener("click", () => { + navigator.clipboard.writeText(listBuddies); + sendtextbox.value = `@${listBuddies}: `; + console.log("Copied to clipboard:", listBuddies); + sendtextbox.focus(); // move cursor into the box + }); + buddies.appendChild(messageElement); buddies.scrollTop = buddies.scrollHeight; - console.log(`Added buddies: ${listBuddies}`) - return ; + console.log(`Added buddies: ${listBuddies}`); +} -}; function waitSocketConnected(socket: Socket): Promise { return new Promise(resolve => { @@ -141,6 +155,27 @@ function waitSocketConnected(socket: Socket): Promise { }); }; +function quitChat (socket: Socket) { + + try { + const systemWindow = document.getElementById('system-box') as HTMLDivElement; + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (socket) { + logout(socket); + setTitle('Chat Page'); + systemWindow.innerHTML = ""; + chatWindow.textContent = ""; + connected(socket); + } else { + getSocket(); + } + } catch (e) { + console.error("Quit Chat error:", e); + showError('Failed to Quit Chat: Unknown error'); + } + +}; + const bconnected = document.getElementById('b-help') as HTMLButtonElement; if (bconnected) { bconnected.click(); @@ -163,7 +198,7 @@ function broadcastMsg (socket: Socket, msgCommand: string[]): void { if (user && socket?.connected) { const message = { command: msgCommand, - destination: "", + destination: '', type: "chat", user: user.name, token: document.cookie, @@ -178,28 +213,61 @@ function broadcastMsg (socket: Socket, msgCommand: string[]): void { async function connected(socket: Socket): Promise { - const buddies = document.getElementById('div-buddies') as HTMLDivElement; - const loggedIn = await isLoggedIn(); - console.log('%cloggedIn:',color.blue, loggedIn?.name); - let oldUser = localStorage.getItem("oldName") ?? ""; - console.log('%coldUser:',color.yellow, oldUser); - if (loggedIn?.name === undefined) {console.log('');return ;} - oldUser = loggedIn.name ?? ""; - const res = await client.guestLogin(); - let user = await updateUser(); - console.log('%cUser?name:',color.yellow, user?.name); - localStorage.setItem("oldName", oldUser); - buddies.textContent = ""; - // if (chatWindow) { - // addMessage('@list - lists all connected users in the chat'); + try { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const loggedIn = await isLoggedIn(); + console.log('%cloggedIn:',color.blue, loggedIn?.name); + let oldUser = localStorage.getItem("oldName") ?? ""; + console.log('%coldUser:',color.yellow, oldUser); + if (loggedIn?.name === undefined) {console.log('');return ;} + oldUser = loggedIn.name ?? ""; + const res = await client.guestLogin(); + let user = await updateUser(); + console.log('%cUser?name:',color.yellow, user?.name); + localStorage.setItem("oldName", oldUser); + buddies.textContent = ""; socket.emit('list', { oldUser: oldUser, user: user?.name, }); - // } - + } catch (e) { + console.error("Login error:", e); + showError('Failed to login: Unknown error'); + } }; +async function whoami(socket: Socket) { + try { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + const loggedIn = await isLoggedIn(); + + const res = await client.guestLogin(); + switch (res.kind) { + case 'success': { + let user = await updateUser(); + if (chatWindow) { + socket.emit('updateClientName', { + oldUser: '', + user: user?.name + }); + } + if (user === null) + return showError('Failed to get user: no user ?'); + setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`); + break; + } + case 'failed': { + showError(`Failed to login: ${res.msg}`); + } + } + } catch (e) { + console.error("Login error:", e); + showError('Failed to login: Unknown error'); + } +}; + + + function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -227,19 +295,17 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn SenderWindowID: socket.id, }; socket.emit('message', JSON.stringify(message)); - // if (systemWindow) { - const messageElement = document.createElement("div"); - messageElement.textContent = `${user}: is connected au server`; - systemWindow.appendChild(messageElement); - systemWindow.scrollTop = systemWindow.scrollHeight; - // } + const messageElement = document.createElement("div"); + messageElement.textContent = `${user}: is connected au server`; + systemWindow.appendChild(messageElement); + systemWindow.scrollTop = systemWindow.scrollHeight; }); // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: { message: ClientMessage}) => { - console.log("Message Obj Recieved:", data); - console.log("%cRecieved data.message.text: ", color.blue, data.message.text); - console.log("%cRecieved data.message.user: ", color.blue, data.message.user); + console.log("%cDEBUG LOGS - Message Obj Recieved:", color.green, data); + console.log("%cDEBUG LOGS - Recieved data.message.text: ", color.green, data.message.text); + console.log("%cDEBUG LOGS - Recieved data.message.user: ", color.green, data.message.user); // Display the message in the chat window const systemWindow = document.getElementById('system-box') as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; @@ -255,6 +321,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; } + if (chatWindow && data.message.destination === "privateMsg") { + const messageElement = document.createElement("div-private"); + messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`; + chatWindow.appendChild(messageElement); + chatWindow.scrollTop = chatWindow.scrollHeight; + } + + const MAX_SYSTEM_MESSAGES = 10; if (systemWindow && data.message.destination === "system-info") { @@ -272,13 +346,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); - socket.on('logout', () => { - const bquit = document.getElementById('b-quit') as HTMLDivElement | null; - if (bquit instanceof HTMLDivElement) { - bquit.click(); - } + socket.on('logout', () => { + quitChat(socket); }); + socket.on('privMessageCopy', (message) => { + addMessage(message); + }) + type Providers = { name: string, display_name: string, @@ -288,7 +363,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn let toggle = false window.addEventListener("focus", () => { - const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; + //nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; if (window.location.pathname === '/app/chat') { console.log("%cWindow is focused on /chat:" + socket.id, color.green); if (socket.id) { @@ -298,6 +373,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn toggle = true; } }); + window.addEventListener("blur", () => { console.log("%cWindow is not focused on /chat", color.red); if (socket.id) @@ -305,6 +381,28 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn toggle = false; }); + + // setInterval(async () => { + // //connected(socket); + // },10000); // every 10 seco + + socket.on('listBud', (myBuddies: string) => { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + console.log('List buddies connected ', myBuddies); + listBuddies(buddies,myBuddies); + }); + + socket.once('welcome', (data) => { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const chatWindow = document.getElementById('t-chatbox') as HTMLDivElement; + chatWindow.innerHTML = ''; + buddies.textContent = ''; + buddies.innerHTML = ''; + connected(socket); + addMessage (`${data.msg} ` + getUser()?.name); + }); + + setTitle('Chat Page'); // Listen for the 'connect' event return { @@ -336,16 +434,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log('unknown response: ', value); } - - socket.once('welcome', (data) => { - chatWindow.textContent = ''; - chatWindow.innerHTML = ''; - buddies.textContent = ''; - buddies.innerHTML = ''; - connected(socket); - addMessage (`${data.msg} ` + getUser()?.name); - }); - // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -358,16 +446,32 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn broadcastMsg(socket, msgCommand); break; case '@who': - bwhoami.click(); - break; + whoami(socket); + break; case '@cls': chatWindow.innerHTML = ''; break; case '@quit': - bquit.click(); + quitChat(socket); break; default: - addMessage('Command not known'); + + const user = getUser()?.name; + // Ensure we have a user AND socket is connected + if (!user || !socket.connected) return; + const message = { + command: msgCommand[0], + destination: '', + type: "chat", + user, + token: document.cookie ?? "", + text: msgCommand[1], + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + //socket.emit('MsgObjectServer', message); + socket.emit('privMessage', JSON.stringify(message)); + // addMessage(JSON.stringify(message)); break; } // Clear the input in all cases @@ -378,35 +482,15 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Clear Text button clearText?.addEventListener("click", () => { - if (chatWindow) { chatWindow.innerHTML = ''; } }); bquit?.addEventListener('click', () => { - if (socket) { - logout(socket); - setTitle('Chat Page'); - systemWindow.innerHTML = ""; - chatWindow.textContent = ""; - connected(socket); - } else { - getSocket(); - } + quitChat(socket); }); - - setInterval(async () => { - //connected(socket); - },10000); // every 10 second - - socket.on('listBud', (myBuddies: string) => { - console.log('List buddies connected ', myBuddies); - listBuddies(buddies,myBuddies); - }); - - // Enter key to send message sendtextbox!.addEventListener('keydown', (event) => { if (event.key === 'Enter') { @@ -416,32 +500,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Whoami button to display user name bwhoami?.addEventListener('click', async () => { - try { - const loggedIn = await isLoggedIn(); - - const res = await client.guestLogin(); - switch (res.kind) { - case 'success': { - let user = await updateUser(); - if (chatWindow) { - socket.emit('updateClientName', { - oldUser: '', - user: user?.name - }); - } - if (user === null) - return showError('Failed to get user: no user ?'); - setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`); - break; - } - case 'failed': { - showError(`Failed to login: ${res.msg}`); - } - } - } catch (e) { - console.error("Login error:", e); - showError('Failed to login: Unknown error'); - } + whoami(socket); }); } } diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 733ba7f..a798de2 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -83,6 +83,8 @@ declare module 'fastify' { io: Server<{ hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; + privMessage: (data: string) => void; + privMessageCopy: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -183,6 +185,33 @@ async function onReady(fastify: FastifyInstance) { }); } + + + function sendPrivMessage(data: ClientMessage, sender?: string) { + fastify.io.fetchSockets().then((sockets) => { + const senderSocket = sockets.find(s => s.id === sender); + for (const s of sockets) { + if (s.id === sender) continue; + const clientInfo = clientChat.get(s.id); + if (!clientInfo?.user) { + console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); + continue; + } + let user: string = clientChat.get(s.id)?.user ?? ""; + const atUser = `@${user}`; + if (atUser !== data.command || atUser === "") { + console.log(color.yellow, `User: '${atUser}' (No user the same is found): '${data.command}' `); + continue; + } + s.emit('MsgObjectServer', { message: data }); + if (senderSocket) + senderSocket.emit('privMessageCopy',`${data.command}: ${data.text}🔒`); + // Debug logs + console.log(color.green, 'Priv to:', clientInfo.user); + } + }); + } + fastify.io.on('connection', (socket: Socket) => { socket.on('message', (message: string) => { @@ -337,6 +366,33 @@ async function onReady(fastify: FastifyInstance) { } }); + + socket.on('privMessage', (data) => { + const clientName: string = clientChat.get(socket.id)?.user || ""; + const prvMessage: ClientMessage = JSON.parse(data) || ""; + console.log( + color.blue, + `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target Name:`, + prvMessage.command + ); + + if (clientName !== null) { + const obj = { + command: prvMessage.command, + destination: 'privateMsg', + type: 'chat', + user: clientName, + token: '', + text: prvMessage.text, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(color.blue, 'PRIV MESSAGE OUT :', obj.SenderWindowID); + sendPrivMessage(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload) diff --git a/src/package.json b/src/package.json index 9fa1e9c..45b7a6d 100644 --- a/src/package.json +++ b/src/package.json @@ -36,7 +36,7 @@ "vite": "^7.2.6" }, "dependencies": { - "@redocly/cli": "^2.12.1", + "@redocly/cli": "^2.12.3", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index ad74292..a50dc94 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.12.1 - version: 2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.3 + version: 2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -943,8 +943,8 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.12.1': - resolution: {integrity: sha512-XGD28QjjZEzN+J9WOROzw4fHNi+Fyw/gCyDZDgI4nX4j9gEBT1PcxN75wWpMoDGHKAUj8ghrhMHtfQoUuR90zg==} + '@redocly/cli@2.12.3': + resolution: {integrity: sha512-1SDW551scNdb4HmNpzyUf4gjsK89KkRUeXF91VVMRkQ5+lFEq1Nj259jN1M25uOd/cg1QjKE3kIbnN1dxPa3ng==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true @@ -958,12 +958,12 @@ packages: resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.12.1': - resolution: {integrity: sha512-xMlKf4dnZsxP3JYBNZFsMNBJqVxWlwLuyGLhGc36hXw50YOla1UjrVZ5psIyzLXgUPI3QJDA1XmGcJ8rcex/ow==} + '@redocly/openapi-core@2.12.3': + resolution: {integrity: sha512-3gdSRftIeUbzXvwDi/tBjO0uj9PzR0XzbWjNwuu3HlVXJ1ElB+K31AnzQ2iA6mjIHq9uvmLRXAs9MsP/0Hbzug==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.12.1': - resolution: {integrity: sha512-ADm+JMHWGYeOwzdGEQ8CYKjmMBLU0ycZTwJbCkQsUulXSNkNA7GzA8lrMM2+I8cPMRk25G5PmtfAR7U+a0o1ew==} + '@redocly/respect-core@2.12.3': + resolution: {integrity: sha512-ZYqrLBlRVVHwgPawOjo94sKmeuuien77xtkXluTa6+y/wkQ8c5oYY7OqWbasMv0IoxSPehwVMa0AL0OCQP3uCQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -2613,10 +2613,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: - react: ^19.2.0 + react: ^19.2.1 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2626,8 +2626,8 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} readable-stream@3.6.2: @@ -3054,6 +3054,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + ulid@3.0.2: + resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} + hasBin: true + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3864,14 +3868,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.12.1(ajv@8.17.1) - '@redocly/respect-core': 2.12.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.3(ajv@8.17.1) + '@redocly/respect-core': 2.12.3(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3883,13 +3887,14 @@ snapshots: https-proxy-agent: 7.0.6(supports-color@10.2.2) mobx: 6.15.0 pluralize: 8.0.0 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - redoc: 2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + redoc: 2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) semver: 7.7.3 set-cookie-parser: 2.7.2 simple-websocket: 9.1.0 - styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + styled-components: 6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + ulid: 3.0.2 undici: 6.22.0 yargs: 17.0.1 transitivePeerDependencies: @@ -3922,7 +3927,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.12.1(ajv@8.17.1)': + '@redocly/openapi-core@2.12.3(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 '@redocly/config': 0.40.0 @@ -3936,12 +3941,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.12.1(ajv@8.17.1)': + '@redocly/respect-core@2.12.3(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.12.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.3(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -5282,21 +5287,21 @@ snapshots: dependencies: obliterator: 2.0.5 - mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: mobx: 6.15.0 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.1(react@19.2.1) - mobx-react@9.2.0(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + mobx-react@9.2.0(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: mobx: 6.15.0 - mobx-react-lite: 4.1.1(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 + mobx-react-lite: 4.1.1(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.1(react@19.2.1) mobx@6.15.0: {} @@ -5661,20 +5666,20 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.0(react@19.2.0): + react-dom@19.2.1(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 scheduler: 0.27.0 react-is@16.13.1: {} - react-tabs@6.1.0(react@19.2.0): + react-tabs@6.1.0(react@19.2.1): dependencies: clsx: 2.1.1 prop-types: 15.8.1 - react: 19.2.0 + react: 19.2.1 - react@19.2.0: {} + react@19.2.1: {} readable-stream@3.6.2: dependencies: @@ -5690,7 +5695,7 @@ snapshots: real-require@0.2.0: {} - redoc@2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)): + redoc@2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)): dependencies: '@redocly/openapi-core': 1.34.5(supports-color@10.2.2) classnames: 2.5.1 @@ -5703,19 +5708,19 @@ snapshots: mark.js: 8.11.1 marked: 4.3.0 mobx: 6.15.0 - mobx-react: 9.2.0(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + mobx-react: 9.2.0(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) openapi-sampler: 1.6.2 path-browserify: 1.0.1 perfect-scrollbar: 1.5.6 polished: 4.3.1 prismjs: 1.30.0 prop-types: 15.8.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-tabs: 6.1.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-tabs: 6.1.0(react@19.2.1) slugify: 1.4.7 stickyfill: 1.1.1 - styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + styled-components: 6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) swagger2openapi: 7.0.8 url-template: 2.0.8 transitivePeerDependencies: @@ -6034,7 +6039,7 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 - styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -6042,8 +6047,8 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.49 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 @@ -6178,6 +6183,8 @@ snapshots: uint8array-extras@1.5.0: {} + ulid@3.0.2: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -6194,9 +6201,9 @@ snapshots: url-template@2.0.8: {} - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 util-deprecate@1.0.2: {} From 4de1aae73cdf27b46a3986e178544a7cfe672180 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Fri, 5 Dec 2025 19:01:42 +0100 Subject: [PATCH 40/66] going home end of day --- frontend/src/pages/chat/chat.ts | 51 +++++++++++++++++---------------- src/chat/src/app.ts | 27 +++++++++-------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 8d380f1..5a514cb 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -58,7 +58,7 @@ function addMessage(text: string) { return ; }; -async function isLoggedIn() { +function isLoggedIn() { return getUser() || null; }; @@ -74,11 +74,13 @@ async function windowStateHidden() { socketId.emit('client_left', { user: userName?.name, why: 'tab window hidden - socket not dead', - }); + }); return; }; -async function windowStateVisable() { +async function windowStateVisable() { + + const buddies = document.getElementById('div-buddies') as HTMLDivElement; const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; console.log("%c WINDOW VISIBLE - oldName :'" + oldName + "'", color.green); @@ -91,6 +93,9 @@ async function windowStateVisable() { userName: oldName, user: user?.name, }); + buddies.innerHTML = ''; + buddies.textContent = ''; + //connected(socketId); setTitle('Chat Page'); return; }; @@ -128,21 +133,21 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { if (!buddies) return; const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement; + const buddiesElement = document.createElement("div-buddies-list"); + buddiesElement.textContent = listBuddies + '\n'; + const user = getUser()?.name ?? ""; - const messageElement = document.createElement("div-buddies-list"); - messageElement.textContent = listBuddies + '\n'; - - - // ✔ Add click-to-copy - messageElement.style.cursor = "pointer"; // optional visual hint - messageElement.addEventListener("click", () => { + buddiesElement.style.cursor = "pointer"; + buddiesElement.addEventListener("click", () => { navigator.clipboard.writeText(listBuddies); - sendtextbox.value = `@${listBuddies}: `; - console.log("Copied to clipboard:", listBuddies); - sendtextbox.focus(); // move cursor into the box + if (listBuddies !== user && user !== "") { + sendtextbox.value = `@${listBuddies}: `; + console.log("Copied to clipboard:", listBuddies); + sendtextbox.focus(); + } }); - buddies.appendChild(messageElement); + buddies.appendChild(buddiesElement); buddies.scrollTop = buddies.scrollHeight; console.log(`Added buddies: ${listBuddies}`); } @@ -150,7 +155,7 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { function waitSocketConnected(socket: Socket): Promise { return new Promise(resolve => { - if (socket.connected) return resolve(); // already connected + if (socket.connected) return resolve(); socket.on("connect", () => resolve()); }); }; @@ -176,10 +181,10 @@ function quitChat (socket: Socket) { }; -const bconnected = document.getElementById('b-help') as HTMLButtonElement; -if (bconnected) { - bconnected.click(); -} +// const bconnected = document.getElementById('b-help') as HTMLButtonElement; +// if (bconnected) { +// bconnected.click(); +// } function logout(socket: Socket) { socket.emit("logout"); // notify server @@ -221,7 +226,7 @@ async function connected(socket: Socket): Promise { console.log('%coldUser:',color.yellow, oldUser); if (loggedIn?.name === undefined) {console.log('');return ;} oldUser = loggedIn.name ?? ""; - const res = await client.guestLogin(); + // const res = await client.guestLogin(); let user = await updateUser(); console.log('%cUser?name:',color.yellow, user?.name); localStorage.setItem("oldName", oldUser); @@ -365,10 +370,10 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn window.addEventListener("focus", () => { //nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; if (window.location.pathname === '/app/chat') { + connected(socket); console.log("%cWindow is focused on /chat:" + socket.id, color.green); if (socket.id) { windowStateVisable(); - connected(socket); } toggle = true; } @@ -385,8 +390,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // setInterval(async () => { // //connected(socket); // },10000); // every 10 seco - - socket.on('listBud', (myBuddies: string) => { + socket.on('listBud', async (myBuddies: string) => { const buddies = document.getElementById('div-buddies') as HTMLDivElement; console.log('List buddies connected ', myBuddies); listBuddies(buddies,myBuddies); @@ -455,7 +459,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn quitChat(socket); break; default: - const user = getUser()?.name; // Ensure we have a user AND socket is connected if (!user || !socket.connected) return; diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index a798de2..f85f2ea 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -132,16 +132,16 @@ async function onReady(fastify: FastifyInstance) { seen.add(username.user); count++; // console.log(color.green,"count: ", count); - console.log(color.yellow, 'Client:', color.reset, username.user); + // console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; io.to(targetSocketId!).emit('listBud', username.user); - console.log( - color.yellow, - 'Chat Socket ID:', - color.reset, - socketId, - ); + // console.log( + // color.yellow, + // 'Chat Socket ID:', + // color.reset, + // socketId, + // ); continue; } @@ -203,11 +203,13 @@ async function onReady(fastify: FastifyInstance) { console.log(color.yellow, `User: '${atUser}' (No user the same is found): '${data.command}' `); continue; } - s.emit('MsgObjectServer', { message: data }); - if (senderSocket) - senderSocket.emit('privMessageCopy',`${data.command}: ${data.text}🔒`); - // Debug logs - console.log(color.green, 'Priv to:', clientInfo.user); + if (data.text !== "") { + s.emit('MsgObjectServer', { message: data }); + if (senderSocket) + senderSocket.emit('privMessageCopy',`${data.command}: ${data.text}🔒`); + // Debug logs + } + console.log(color.green, 'Priv to:', data.text); } }); } @@ -361,6 +363,7 @@ async function onReady(fastify: FastifyInstance) { SenderWindowID: socket.id, }; console.log(color.blue, 'BROADCASTS OUT :', obj.SenderWindowID); + broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } From b2867757b620d41c76307100ca95bea34c6be768 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Sat, 6 Dec 2025 15:33:11 +0100 Subject: [PATCH 41/66] Added profil popup - WIP page opens on double click of the name of buddy --- frontend/src/chat/chat.css | 29 +++++++++++++++++++ frontend/src/pages/chat/chat.html | 6 ++++ frontend/src/pages/chat/chat.ts | 48 ++++++++++++++++++++++++++++--- src/chat/src/app.ts | 48 +++++++++++-------------------- 4 files changed, 95 insertions(+), 36 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 9c3bccd..e12d5f3 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -4,6 +4,8 @@ src: url("/fonts/NimbusMonoL.woff2") format("woff2"); } +@tailwind utilities; + .btn-style { @apply w-[100px] @@ -189,4 +191,31 @@ div-private { @apply text-blue-800; +} + +.popUpBox { + @apply + bg-white + p-6 rounded-xl + shadow-xl + w-[800px] + h-[350px] + p-[10px] + border-1 + border-black + +} + +.profilPopup { + @apply + fixed + inset-0 + bg-black/50 + flex + justify-center + items-center; +} + +.hidden{ + display: none; } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 2882ae5..6314eff 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -31,6 +31,12 @@

Charlie

-->
+
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 5a514cb..406a731 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -110,13 +110,23 @@ function parseCmdMsg(msgText: string): string[] | undefined { command[1] = msgText; return command; } - const noArgCommands = ['@quit', '@cls', '@profile']; + const noArgCommands = ['@quit', '@who', '@cls']; if (noArgCommands.includes(msgText)) { command[0] = msgText; command[1] = ''; return command; } - const colonIndex = msgText.indexOf(":"); + + const ArgCommands = ['@profil', '@block']; + const userName = msgText.indexOf(" "); + const cmd2 = msgText.slice(0, userName).trim() ?? ""; + const user = msgText.slice(userName + 1).trim(); + if (ArgCommands.includes(cmd2)) { + command[0] = cmd2; + command[1] = user; + return command; + } + const colonIndex = msgText.indexOf(":"); if (colonIndex === -1) { command[0] = msgText; command[1] = ''; @@ -147,6 +157,12 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { } }); + buddiesElement.addEventListener("dblclick", () => { + console.log("Open profile:", listBuddies); + openProfilePopup(`Profil: ${listBuddies}`); + + }); + buddies.appendChild(buddiesElement); buddies.scrollTop = buddies.scrollHeight; console.log(`Added buddies: ${listBuddies}`); @@ -244,7 +260,7 @@ async function connected(socket: Socket): Promise { async function whoami(socket: Socket) { try { const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; - const loggedIn = await isLoggedIn(); + const loggedIn = isLoggedIn(); const res = await client.guestLogin(); switch (res.kind) { @@ -271,6 +287,15 @@ async function whoami(socket: Socket) { } }; +async function openProfilePopup(profil: string) { + + const modalname = document.getElementById("modal-name") ?? null; + if (modalname) + modalname.innerHTML = profil; + const profilList = document.getElementById("profile-modal") ?? null; + if (profilList) + profilList.classList.remove("hidden"); +} function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -438,6 +463,16 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log('unknown response: ', value); } + + + const buttonPro = document.getElementById("close-modal") ?? null; + + if (buttonPro) + buttonPro.addEventListener("click", () => { + const profilList = document.getElementById("profile-modal") ?? null; + if (profilList) profilList.classList.add("hidden"); + }); + // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -452,6 +487,10 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn case '@who': whoami(socket); break; + case '@profil': + if (`${msgCommand[1]}`) + openProfilePopup(`Profil: ${msgCommand[1]}`); + break; case '@cls': chatWindow.innerHTML = ''; break; @@ -501,7 +540,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }); - // Whoami button to display user name + // Whoami button to display user name addMessage(msgCommand[0]); + bwhoami?.addEventListener('click', async () => { whoami(socket); }); diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index f85f2ea..d2a7cae 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -144,43 +144,32 @@ async function onReady(fastify: FastifyInstance) { // ); continue; } - // If no io provided, assume entries in the map are valid and count them. count++; - console.log( - color.red, - 'Client (unverified):', - color.reset, - username, - ); - console.log( - color.red, - 'Chat Socket ID (unverified):', - color.reset, - socketId, - ); + console.log(color.red, 'DEBUG LOG: - Client (unverified):', color.reset, username ); + console.log(color.red, 'DEBUG LOG: - Chat Socket ID (unverified):', color.reset, socketId); } return count; } function broadcast(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { - for (const s of sockets) { + for (const socket of sockets) { // Skip sender's own socket - if (s.id === sender) continue; + if (socket.id === sender) continue; // Get client name from map - const clientInfo = clientChat.get(s.id); + const clientInfo = clientChat.get(socket.id); if (!clientInfo?.user) { - console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); + console.log(color.yellow, `Skipping socket ${socket.id} (no user found)`); continue; } // Emit structured JSON object - s.emit('MsgObjectServer', { message: data }); + socket.emit('MsgObjectServer', { message: data }); // Debug logs - console.log(color.green, 'Broadcast to:', clientInfo.user); - console.log(' Target socket ID:', s.id); - console.log(' Target rooms:', [...s.rooms]); - console.log(' Sender socket ID:', sender ?? 'none'); + console.log(color.green, `'Broadcast to:', ${data.command} message: ${data.text}`); + // console.log('DEBUG - Target socket ID:', s.id); + // console.log('DEBUG - Target rooms:', [...s.rooms]); + // console.log('DEBUG - Sender socket ID:', sender ?? 'none'); } }); } @@ -200,16 +189,17 @@ async function onReady(fastify: FastifyInstance) { let user: string = clientChat.get(s.id)?.user ?? ""; const atUser = `@${user}`; if (atUser !== data.command || atUser === "") { - console.log(color.yellow, `User: '${atUser}' (No user the same is found): '${data.command}' `); + console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command NOT FOUND: '${data.command[0]}' `); continue; } if (data.text !== "") { s.emit('MsgObjectServer', { message: data }); + console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command FOUND: '${data.command}' `); if (senderSocket) senderSocket.emit('privMessageCopy',`${data.command}: ${data.text}🔒`); // Debug logs } - console.log(color.green, 'Priv to:', data.text); + console.log(color.green, `'Priv to:', ${data.command} message: ${data.text}`); } }); } @@ -244,12 +234,7 @@ async function onReady(fastify: FastifyInstance) { // Send object directly — DO NOT wrap it in a string broadcast(obj, obj.SenderWindowID); - console.log( - color.red, - 'connected in the Chat :', - connectedUser(fastify.io), - color.reset, - ); + console.log(color.red, 'DEBUG LOG: connected in the Chat :', connectedUser(fastify.io), color.reset); }); socket.on('testend', (sock_id_cl: string) => { @@ -288,8 +273,7 @@ async function onReady(fastify: FastifyInstance) { // }; if (client) { client.user = userFromFrontend.user; - console.log(color.green, 'client.user is: ', client.user); - + console.log(color.green, `'DEBUG LOG: client.user is, '${client.user}'`); } } }); From 8f3bedf65a2c3905c9d5fd2dac4be088ed073a5a Mon Sep 17 00:00:00 2001 From: NigeParis Date: Sat, 6 Dec 2025 16:33:28 +0100 Subject: [PATCH 42/66] getProfil Function return html for profil page --- frontend/src/chat/chat.css | 4 ++-- frontend/src/pages/chat/chat.html | 4 ++-- frontend/src/pages/chat/chat.ts | 25 ++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index e12d5f3..d98a4fb 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -1,8 +1,8 @@ @import "tailwindcss"; -@font-face { +/* @font-face { font-family: "Nimbus Mono L"; src: url("/fonts/NimbusMonoL.woff2") format("woff2"); -} +} */ @tailwind utilities; diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 6314eff..bf06259 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -28,12 +28,12 @@
+

Charlie

-->Marks
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 406a731..ac7ec31 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -58,10 +58,20 @@ function addMessage(text: string) { return ; }; +function clearText() { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (!chatWindow) return; + chatWindow.innerHTML = ""; +} + function isLoggedIn() { return getUser() || null; }; +function getProfil(user: string): string { + return `Profil: ${user}
` +} + async function windowStateHidden() { const socketId = __socket || undefined; // let oldName = localStorage.getItem("oldName") ?? undefined; @@ -159,8 +169,14 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { buddiesElement.addEventListener("dblclick", () => { console.log("Open profile:", listBuddies); - openProfilePopup(`Profil: ${listBuddies}`); - + const profile: string = getProfil(listBuddies); + openProfilePopup(`${profile}`); + setTimeout(() => { + const clearTextBtn = document.querySelector("#popup-b-clear"); + clearTextBtn?.addEventListener("click", () => { + clearText(); + }); + }, 0) }); buddies.appendChild(buddiesElement); @@ -289,12 +305,15 @@ async function whoami(socket: Socket) { async function openProfilePopup(profil: string) { + const modalname = document.getElementById("modal-name") ?? null; if (modalname) - modalname.innerHTML = profil; + modalname.innerHTML = `${profil}`; const profilList = document.getElementById("profile-modal") ?? null; if (profilList) profilList.classList.remove("hidden"); + // The popup now exists → attach the event + } From 3aa1f6fb2d643b262b538a64f66b5e862fa93b9b Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 8 Dec 2025 15:43:43 +0100 Subject: [PATCH 43/66] profil displays basic info if guest or not --- frontend/src/chat/chat.css | 7 +++ frontend/src/pages/chat/chat.ts | 73 +++++++++++++++++----- src/chat/src/app.ts | 107 ++++++++++++++++++++++++++++---- 3 files changed, 159 insertions(+), 28 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index d98a4fb..5e9b65e 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -216,6 +216,13 @@ div-private { items-center; } +.popup-b-clear { + @apply + absolute + bottom-42 + right-12 +} + .hidden{ display: none; } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index ac7ec31..4b8bd2f 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -68,8 +68,30 @@ function isLoggedIn() { return getUser() || null; }; -function getProfil(user: string): string { - return `Profil: ${user}
` +function actionBtnPopUp() { + setTimeout(() => { + const clearTextBtn = document.querySelector("#popup-b-clear"); + clearTextBtn?.addEventListener("click", () => { + clearText(); + }); + }, 0) +} + +// getProfil get the profil of user +function getProfil(socket: Socket, user: string) { + if (!socket.connected) return; + const profil = { + command: '@profil', + destination: 'profilMessage', + type: "chat", + user: user, + token: document.cookie ?? "", + text: user, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + // addMessage(JSON.stringify(profil)); + socket.emit('profilMessage', JSON.stringify(profil)); } async function windowStateHidden() { @@ -149,7 +171,7 @@ function parseCmdMsg(msgText: string): string[] | undefined { return command; } -async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { +async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: string) { if (!buddies) return; const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement; @@ -169,14 +191,15 @@ async function listBuddies(buddies: HTMLDivElement, listBuddies: string) { buddiesElement.addEventListener("dblclick", () => { console.log("Open profile:", listBuddies); - const profile: string = getProfil(listBuddies); - openProfilePopup(`${profile}`); - setTimeout(() => { - const clearTextBtn = document.querySelector("#popup-b-clear"); - clearTextBtn?.addEventListener("click", () => { - clearText(); - }); - }, 0) + getProfil(socket, listBuddies); + // openProfilePopup(`${profile}`); + // setTimeout(() => { + // const clearTextBtn = document.querySelector("#popup-b-clear"); + // clearTextBtn?.addEventListener("click", () => { + // clearText(); + // }); + // }, 0) + // actionBtnPopUp(); }); buddies.appendChild(buddiesElement); @@ -313,7 +336,6 @@ async function openProfilePopup(profil: string) { if (profilList) profilList.classList.remove("hidden"); // The popup now exists → attach the event - } @@ -394,7 +416,12 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log("Getuser():", getUser()); }); + socket.on('profilMessage', (profil) => { + openProfilePopup(profil); + actionBtnPopUp(); + }); + socket.on('logout', () => { quitChat(socket); }); @@ -437,7 +464,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn socket.on('listBud', async (myBuddies: string) => { const buddies = document.getElementById('div-buddies') as HTMLDivElement; console.log('List buddies connected ', myBuddies); - listBuddies(buddies,myBuddies); + listBuddies(socket, buddies, myBuddies); }); socket.once('welcome', (data) => { @@ -507,8 +534,22 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn whoami(socket); break; case '@profil': - if (`${msgCommand[1]}`) - openProfilePopup(`Profil: ${msgCommand[1]}`); + getProfil(socket, msgCommand[1]); + // Ensure we have a user AND socket is connected + // if (!socket.connected) return; + // const profil = { + // command: msgCommand[0], + // destination: 'profil', + // type: "chat", + // user: msgCommand[1], + // token: document.cookie ?? "", + // text: msgCommand[1], + // timestamp: Date.now(), + // SenderWindowID: socket.id, + // }; + // //socket.emit('MsgObjectServer', message); + // addMessage(JSON.stringify(profil)); + // socket.emit('profilMessage', JSON.stringify(profil)); break; case '@cls': chatWindow.innerHTML = ''; @@ -524,7 +565,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn command: msgCommand[0], destination: '', type: "chat", - user, + user: user, token: document.cookie ?? "", text: msgCommand[1], timestamp: Date.now(), diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index d2a7cae..a02fac9 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -6,6 +6,7 @@ import * as auth from '@shared/auth'; import * as swagger from '@shared/swagger'; import * as utils from '@shared/utils'; import { Server, Socket } from 'socket.io'; +import type { User } from '@shared/database/mixin/user'; // colors for console.log export const color = { @@ -84,6 +85,7 @@ declare module 'fastify' { hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; privMessage: (data: string) => void; + profilMessage: (data: string) => void; privMessageCopy: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; @@ -146,7 +148,7 @@ async function onReady(fastify: FastifyInstance) { } // If no io provided, assume entries in the map are valid and count them. count++; - console.log(color.red, 'DEBUG LOG: - Client (unverified):', color.reset, username ); + console.log(color.red, 'DEBUG LOG: - Client (unverified):', color.reset, username); console.log(color.red, 'DEBUG LOG: - Chat Socket ID (unverified):', color.reset, socketId); } return count; @@ -174,7 +176,58 @@ async function onReady(fastify: FastifyInstance) { }); } + function formatTimestamp(ms: number) { + const d = new Date(ms); + return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); + } + function getUserByName(users: User[], name: string) { + return users.find(u => u.name === name) || null; + } + + + // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... + async function getProfil(user: string): Promise { + let profilHtmlPopup = '404: Error: Profil not found'; + const sockets = await fastify.io.fetchSockets(); + const users: User[] = fastify.db.getAllUsers() ?? []; + + // const senderSocket = sockets.find(socket => socket.id === user); + for (const socket of sockets) { + const clientInfo = clientChat?.get(socket.id); + const allUsers: User | null = getUserByName(users, user); + if (clientInfo?.user === allUsers?.name) { + const lastSeen = formatTimestamp(clientInfo?.lastSeen ?? 0); + console.log(color.yellow, `'Clientinfo.user: '${lastSeen}' user: '${user}'`); + profilHtmlPopup = `
+ Profil of ${clientInfo?.user}
+ Login Name: '${allUsers?.login ?? 'Guest'}'
+
+ +
Joined: xx/xx/xx
+
Last connection: ${lastSeen}
+
About: No description
+ `; + break; + } + }; + return profilHtmlPopup; + }; + + function sendProfil(data: ClientMessage, clientProfil?: string) { + + fastify.io.fetchSockets().then((sockets) => { + const senderSocket = sockets.find(socket => socket.id === clientProfil); + for (const socket of sockets) { + const clientInfo = clientChat.get(socket.id); + if (clientInfo?.user === data.user) { + if (senderSocket) { + socket.emit('profilMessage', `${data.text}`); + } + } + } + }); + } function sendPrivMessage(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { @@ -186,20 +239,20 @@ async function onReady(fastify: FastifyInstance) { console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); continue; } - let user: string = clientChat.get(s.id)?.user ?? ""; + const user: string = clientChat.get(s.id)?.user ?? ''; const atUser = `@${user}`; - if (atUser !== data.command || atUser === "") { + if (atUser !== data.command || atUser === '') { console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command NOT FOUND: '${data.command[0]}' `); continue; } - if (data.text !== "") { + if (data.text !== '') { s.emit('MsgObjectServer', { message: data }); console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command FOUND: '${data.command}' `); - if (senderSocket) - senderSocket.emit('privMessageCopy',`${data.command}: ${data.text}🔒`); - // Debug logs + if (senderSocket) { + senderSocket.emit('privMessageCopy', `${data.command}: ${data.text}🔒`); + } } - console.log(color.green, `'Priv to:', ${data.command} message: ${data.text}`); + console.log(color.green, `DEBUG LOG: 'Priv to:', ${data.command} message: ${data.text}`); } }); } @@ -355,12 +408,12 @@ async function onReady(fastify: FastifyInstance) { socket.on('privMessage', (data) => { - const clientName: string = clientChat.get(socket.id)?.user || ""; - const prvMessage: ClientMessage = JSON.parse(data) || ""; + const clientName: string = clientChat.get(socket.id)?.user || ''; + const prvMessage: ClientMessage = JSON.parse(data) || ''; console.log( color.blue, `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target Name:`, - prvMessage.command + prvMessage.command, ); if (clientName !== null) { @@ -374,12 +427,42 @@ async function onReady(fastify: FastifyInstance) { timestamp: Date.now(), SenderWindowID: socket.id, }; - console.log(color.blue, 'PRIV MESSAGE OUT :', obj.SenderWindowID); + console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID); sendPrivMessage(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } }); + socket.on('profilMessage', async (data) => { + const clientName: string = clientChat.get(socket.id)?.user || ''; + const profilMessage: ClientMessage = JSON.parse(data) || ''; + const users: User[] = fastify.db.getAllUsers() ?? []; + console.log(color.yellow, 'DEBUG LOG: ALL USERS EVER CONNECTED:', users); + console.log( + color.blue, + `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`, + profilMessage.user, + ); + const profileHtml: string = await getProfil(profilMessage.user); + if (clientName !== null) { + const testuser: User | null = getUserByName(users, profilMessage.user); + console.log(color.yellow, 'user:', testuser?.login ?? 'Guest'); + const obj = { + command: profilMessage.command, + destination: 'profilMsg', + type: 'chat', + user: clientName, + token: '', + text: profileHtml, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', obj.SenderWindowID); + sendProfil(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload) From 71c5914e0483b7640966e6a8b5cb461a93ccbe4a Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 8 Dec 2025 16:26:39 +0100 Subject: [PATCH 44/66] profil now displays info on players not connected --- src/chat/src/app.ts | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index a02fac9..033e17c 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -176,10 +176,10 @@ async function onReady(fastify: FastifyInstance) { }); } - function formatTimestamp(ms: number) { - const d = new Date(ms); - return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); - } + // function formatTimestamp(ms: number) { + // const d = new Date(ms); + // return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); + // } function getUserByName(users: User[], name: string) { return users.find(u => u.name === name) || null; @@ -189,28 +189,20 @@ async function onReady(fastify: FastifyInstance) { // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... async function getProfil(user: string): Promise { let profilHtmlPopup = '404: Error: Profil not found'; - const sockets = await fastify.io.fetchSockets(); const users: User[] = fastify.db.getAllUsers() ?? []; + const allUsers: User | null = getUserByName(users, user); + console.log(color.yellow, `'userFound is:'${allUsers?.name}`); + if (user === allUsers?.name) { + console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`); + profilHtmlPopup = `
+ Profil of ${allUsers.name}
+ Login Name: '${allUsers?.login ?? 'Guest'}' +
+ +
About: No description
+ `; + } - // const senderSocket = sockets.find(socket => socket.id === user); - for (const socket of sockets) { - const clientInfo = clientChat?.get(socket.id); - const allUsers: User | null = getUserByName(users, user); - if (clientInfo?.user === allUsers?.name) { - const lastSeen = formatTimestamp(clientInfo?.lastSeen ?? 0); - console.log(color.yellow, `'Clientinfo.user: '${lastSeen}' user: '${user}'`); - profilHtmlPopup = `
- Profil of ${clientInfo?.user}
- Login Name: '${allUsers?.login ?? 'Guest'}' -
- -
Joined: xx/xx/xx
-
Last connection: ${lastSeen}
-
About: No description
- `; - break; - } - }; return profilHtmlPopup; }; From e0610ca00103d8e7614e7ffa874d0cf5e466125e Mon Sep 17 00:00:00 2001 From: NigeParis Date: Mon, 8 Dec 2025 16:43:42 +0100 Subject: [PATCH 45/66] Added Pong invitation Btn - WIP no implementation yet --- frontend/src/chat/chat.css | 7 +++++++ src/chat/src/app.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 5e9b65e..993c029 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -223,6 +223,13 @@ div-private { right-12 } +.popup-b-invite { + @apply + absolute + bottom-52 + right-12 +} + .hidden{ display: none; } \ No newline at end of file diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 033e17c..b980883 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -199,6 +199,7 @@ async function onReady(fastify: FastifyInstance) { Login Name: '${allUsers?.login ?? 'Guest'}'
+
About: No description
`; } From 74f96d6568de54f4c42f15bc6736437abade187a Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 9 Dec 2025 13:27:50 +0100 Subject: [PATCH 46/66] getProfil() returns profil under an object form --- frontend/src/pages/chat/chat.ts | 72 ++++++++++++++++++------------ src/chat/src/app.ts | 78 ++++++++++++++++++--------------- 2 files changed, 85 insertions(+), 65 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 4b8bd2f..61eee91 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -22,6 +22,18 @@ export type ClientMessage = { }; +export type ClientProfil = { + command: string, + destination: string, + type: string, + user: string, + loginName: string, + userID: string, + text: string, + timestamp: number, + SenderWindowID:string, +}; + // get the name of the machine used to connect const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); @@ -68,14 +80,28 @@ function isLoggedIn() { return getUser() || null; }; -function actionBtnPopUp() { +function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) { + + + +}; + + + +function actionBtnPopUp(profil: ClientProfil, senderSocket: Socket) { setTimeout(() => { const clearTextBtn = document.querySelector("#popup-b-clear"); clearTextBtn?.addEventListener("click", () => { clearText(); }); + const InvitePongBtn = document.querySelector("#popup-b-invite"); + InvitePongBtn?.addEventListener("click", () => { + inviteToPlayPong(profil, senderSocket); + }); + + }, 0) -} +}; // getProfil get the profil of user function getProfil(socket: Socket, user: string) { @@ -192,14 +218,6 @@ async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: buddiesElement.addEventListener("dblclick", () => { console.log("Open profile:", listBuddies); getProfil(socket, listBuddies); - // openProfilePopup(`${profile}`); - // setTimeout(() => { - // const clearTextBtn = document.querySelector("#popup-b-clear"); - // clearTextBtn?.addEventListener("click", () => { - // clearText(); - // }); - // }, 0) - // actionBtnPopUp(); }); buddies.appendChild(buddiesElement); @@ -326,12 +344,23 @@ async function whoami(socket: Socket) { } }; -async function openProfilePopup(profil: string) { +async function openProfilePopup(profil: ClientProfil) { const modalname = document.getElementById("modal-name") ?? null; if (modalname) - modalname.innerHTML = `${profil}`; + modalname.innerHTML = ` +
+ Profil of ${profil.user}
+ Login Name: '${profil.loginName ?? 'Guest'}' +
+ Login ID: '${profil.userID ?? ''}' +
+ + +
About: '${profil.text}'
+ + `; const profilList = document.getElementById("profile-modal") ?? null; if (profilList) profilList.classList.remove("hidden"); @@ -416,9 +445,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log("Getuser():", getUser()); }); - socket.on('profilMessage', (profil) => { + socket.on('profilMessage', (profil: ClientProfil) => { openProfilePopup(profil); - actionBtnPopUp(); + actionBtnPopUp(profil, socket); }); @@ -535,21 +564,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn break; case '@profil': getProfil(socket, msgCommand[1]); - // Ensure we have a user AND socket is connected - // if (!socket.connected) return; - // const profil = { - // command: msgCommand[0], - // destination: 'profil', - // type: "chat", - // user: msgCommand[1], - // token: document.cookie ?? "", - // text: msgCommand[1], - // timestamp: Date.now(), - // SenderWindowID: socket.id, - // }; - // //socket.emit('MsgObjectServer', message); - // addMessage(JSON.stringify(profil)); - // socket.emit('profilMessage', JSON.stringify(profil)); break; case '@cls': chatWindow.innerHTML = ''; diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index b980883..4c877a8 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -42,6 +42,19 @@ export type ClientMessage = { SenderWindowID: string; }; + +export type ClientProfil = { + command?: string, + destination?: string, + type?: string, + user?: string, + loginName?: string, + userID?: string, + text?: string, + timestamp?: number, + SenderWindowID?:string, +}; + const clientChat = new Map(); // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... @@ -85,7 +98,7 @@ declare module 'fastify' { hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; privMessage: (data: string) => void; - profilMessage: (data: string) => void; + profilMessage: (data: ClientProfil) => void; privMessageCopy: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; @@ -185,39 +198,42 @@ async function onReady(fastify: FastifyInstance) { return users.find(u => u.name === name) || null; } + function getAboutPlayer( ): string{ + const description = 'Player is good Shape this needs to be replaced by a bd.function()'; + return description; + } // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... - async function getProfil(user: string): Promise { - let profilHtmlPopup = '404: Error: Profil not found'; + async function getProfil(user: string, socket: Socket): Promise { + + let clientProfil: ClientProfil = {}; const users: User[] = fastify.db.getAllUsers() ?? []; const allUsers: User | null = getUserByName(users, user); console.log(color.yellow, `'userFound is:'${allUsers?.name}`); if (user === allUsers?.name) { console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`); - profilHtmlPopup = `
- Profil of ${allUsers.name}
- Login Name: '${allUsers?.login ?? 'Guest'}' -
- - -
About: No description
- `; - } - return profilHtmlPopup; + clientProfil = { + command: 'getProfil', + destination: 'profilMsg', + type: 'chat' as const, + user: `${allUsers.name}`, + loginName: `${allUsers?.login ?? 'Guest'}`, + userID: `${allUsers?.id ?? ''}`, + text: getAboutPlayer(), + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + } + return clientProfil; }; - function sendProfil(data: ClientMessage, clientProfil?: string) { - + function sendProfil(data: ClientProfil, clientProfil?: string) { fastify.io.fetchSockets().then((sockets) => { const senderSocket = sockets.find(socket => socket.id === clientProfil); - for (const socket of sockets) { - const clientInfo = clientChat.get(socket.id); - if (clientInfo?.user === data.user) { - if (senderSocket) { - socket.emit('profilMessage', `${data.text}`); - } - } + if (senderSocket) { + console.log(color.yellow, 'user inFO:', data.user); + senderSocket.emit('profilMessage', data); } }); } @@ -436,22 +452,12 @@ async function onReady(fastify: FastifyInstance) { `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`, profilMessage.user, ); - const profileHtml: string = await getProfil(profilMessage.user); + const profileHtml: ClientProfil = await getProfil(profilMessage.user, socket); if (clientName !== null) { const testuser: User | null = getUserByName(users, profilMessage.user); - console.log(color.yellow, 'user:', testuser?.login ?? 'Guest'); - const obj = { - command: profilMessage.command, - destination: 'profilMsg', - type: 'chat', - user: clientName, - token: '', - text: profileHtml, - timestamp: Date.now(), - SenderWindowID: socket.id, - }; - console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', obj.SenderWindowID); - sendProfil(obj, obj.SenderWindowID); + console.log(color.yellow, 'user:', testuser?.name ?? 'Guest'); + console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', profileHtml.SenderWindowID); + sendProfil(profileHtml, profileHtml.SenderWindowID); // clientChat.delete(obj.user); } }); From af957bc74be8561f47096e80138a9c3e997de6b0 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 9 Dec 2025 17:50:57 +0100 Subject: [PATCH 47/66] invitation working by chat --- frontend/src/pages/chat/chat.ts | 60 ++++++++++++----- src/chat/src/app.ts | 113 ++++++++++++++++++++++---------- 2 files changed, 122 insertions(+), 51 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 61eee91..8d75d33 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -32,8 +32,25 @@ export type ClientProfil = { text: string, timestamp: number, SenderWindowID:string, + SenderName: string, + innerHtml?: string, + }; +// export type inviteGame = { +// command?: string, +// destination?: string, +// type?: string, +// user?: string, +// loginName?: string, +// userID?: string, +// innerHtml?: string, +// timestamp?: number, +// SenderWindowID?:string, +// }; + + + // get the name of the machine used to connect const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); @@ -81,28 +98,35 @@ function isLoggedIn() { }; function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) { - - - + profil.SenderName = getUser()?.name ?? ''; + if (profil.SenderName === profil.user) return; + addMessage(`You invited to play: ${profil.user}🏓`) + senderSocket.emit('inviteGame', JSON.stringify(profil)); }; -function actionBtnPopUp(profil: ClientProfil, senderSocket: Socket) { +function actionBtnPopUpClear(profil: ClientProfil) { setTimeout(() => { const clearTextBtn = document.querySelector("#popup-b-clear"); clearTextBtn?.addEventListener("click", () => { clearText(); }); - const InvitePongBtn = document.querySelector("#popup-b-invite"); - InvitePongBtn?.addEventListener("click", () => { - inviteToPlayPong(profil, senderSocket); - }); - - }, 0) }; + +function actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) { + setTimeout(() => { + const InvitePongBtn = document.querySelector("#popup-b-invite"); + InvitePongBtn?.addEventListener("click", () => { + inviteToPlayPong(invite, senderSocket); + }); + }, 0) +}; + + + // getProfil get the profil of user function getProfil(socket: Socket, user: string) { if (!socket.connected) return; @@ -357,7 +381,7 @@ async function openProfilePopup(profil: ClientProfil) { Login ID: '${profil.userID ?? ''}'
- +
About: '${profil.text}'
`; @@ -403,9 +427,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: { message: ClientMessage}) => { - console.log("%cDEBUG LOGS - Message Obj Recieved:", color.green, data); - console.log("%cDEBUG LOGS - Recieved data.message.text: ", color.green, data.message.text); - console.log("%cDEBUG LOGS - Recieved data.message.user: ", color.green, data.message.user); // Display the message in the chat window const systemWindow = document.getElementById('system-box') as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; @@ -447,10 +468,17 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn socket.on('profilMessage', (profil: ClientProfil) => { openProfilePopup(profil); - actionBtnPopUp(profil, socket); + actionBtnPopUpClear(profil); + actionBtnPopUpInvite(profil, socket); + }); + + socket.on('inviteGame', (invite: ClientProfil) => { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + const messageElement = document.createElement("div"); + messageElement.innerHTML =`🏓${invite.SenderName}: ${invite.innerHtml}`; + chatWindow.appendChild(messageElement); }); - socket.on('logout', () => { quitChat(socket); }); diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 4c877a8..14258e2 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -42,18 +42,35 @@ export type ClientMessage = { SenderWindowID: string; }; - export type ClientProfil = { - command?: string, - destination?: string, - type?: string, - user?: string, - loginName?: string, - userID?: string, - text?: string, - timestamp?: number, - SenderWindowID?:string, -}; + command: string, + destination: string, + type: string, + user: string, + loginName: string, + userID: string, + text: string, + timestamp: number, + SenderWindowID:string, + SenderName: string, + innerHtml?: string, + +}; + + +// export type inviteGame = { +// command?: string, +// destination?: string, +// type?: string, +// user?: string, +// loginName?: string, +// userID?: string, +// innerHtml?: string, +// timestamp?: number, +// SenderWindowID?:string, +// SenderName: string +// }; + const clientChat = new Map(); @@ -99,6 +116,7 @@ declare module 'fastify' { MsgObjectServer: (data: { message: ClientMessage }) => void; privMessage: (data: string) => void; profilMessage: (data: ClientProfil) => void; + inviteGame: (data: ClientProfil) => void; privMessageCopy: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; @@ -130,15 +148,12 @@ async function onReady(fastify: FastifyInstance) { // If we have the io instance, attempt to validate the socket is still connected if (io && typeof io.sockets?.sockets?.get === 'function') { - const s = io.sockets.sockets.get(socketId) as - | Socket - | undefined; + const s = io.sockets.sockets.get(socketId) as Socket | undefined; // If socket not found or disconnected, remove from map and skip if (!s || s.disconnected) { clientChat.delete(socketId); continue; } - // Skip duplicates (DO NOT delete them — just don't count) if (seen.has(username.user)) { continue; @@ -146,23 +161,11 @@ async function onReady(fastify: FastifyInstance) { // socket exists and is connected seen.add(username.user); count++; - // console.log(color.green,"count: ", count); - // console.log(color.yellow, 'Client:', color.reset, username.user); - const targetSocketId = target; io.to(targetSocketId!).emit('listBud', username.user); - // console.log( - // color.yellow, - // 'Chat Socket ID:', - // color.reset, - // socketId, - // ); continue; } - // If no io provided, assume entries in the map are valid and count them. count++; - console.log(color.red, 'DEBUG LOG: - Client (unverified):', color.reset, username); - console.log(color.red, 'DEBUG LOG: - Chat Socket ID (unverified):', color.reset, socketId); } return count; } @@ -182,9 +185,6 @@ async function onReady(fastify: FastifyInstance) { socket.emit('MsgObjectServer', { message: data }); // Debug logs console.log(color.green, `'Broadcast to:', ${data.command} message: ${data.text}`); - // console.log('DEBUG - Target socket ID:', s.id); - // console.log('DEBUG - Target rooms:', [...s.rooms]); - // console.log('DEBUG - Sender socket ID:', sender ?? 'none'); } }); } @@ -195,34 +195,44 @@ async function onReady(fastify: FastifyInstance) { // } function getUserByName(users: User[], name: string) { - return users.find(u => u.name === name) || null; + return users.find(user => user.name === name) || null; } + ///TODO find the description info for profil / or profil game link and return function getAboutPlayer( ): string{ const description = 'Player is good Shape this needs to be replaced by a bd.function()'; return description; } + function getGameLink(): string{ + const link = `Click me`; + return link; + } + + // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... async function getProfil(user: string, socket: Socket): Promise { - let clientProfil: ClientProfil = {}; + let clientProfil!: ClientProfil; const users: User[] = fastify.db.getAllUsers() ?? []; const allUsers: User | null = getUserByName(users, user); console.log(color.yellow, `'userFound is:'${allUsers?.name}`); if (user === allUsers?.name) { console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`); - clientProfil = { + clientProfil = + { command: 'getProfil', destination: 'profilMsg', - type: 'chat' as const, + type: 'chat' as const, user: `${allUsers.name}`, loginName: `${allUsers?.login ?? 'Guest'}`, userID: `${allUsers?.id ?? ''}`, text: getAboutPlayer(), timestamp: Date.now(), SenderWindowID: socket.id, + SenderName: '', + innerHtml: '', }; } return clientProfil; @@ -238,6 +248,26 @@ async function onReady(fastify: FastifyInstance) { }); } + function sendInvite(innerHtml: string, data: ClientProfil) { + fastify.io.fetchSockets().then((sockets) => { + + let targetSocket; + for (const socket of sockets) { + let clientInfo: string = clientChat.get(socket.id)?.user || ''; + if (clientInfo === data.user) { + console.log(color.yellow, 'FOUND:', data.user); + targetSocket = socket || ''; + break; + } + } + data.innerHtml = innerHtml ?? ''; + if (targetSocket) { + targetSocket.emit('inviteGame', data); + } + }); + } + + function sendPrivMessage(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { const senderSocket = sockets.find(s => s.id === sender); @@ -442,7 +472,7 @@ async function onReady(fastify: FastifyInstance) { } }); - socket.on('profilMessage', async (data) => { + socket.on('profilMessage', async (data: string) => { const clientName: string = clientChat.get(socket.id)?.user || ''; const profilMessage: ClientMessage = JSON.parse(data) || ''; const users: User[] = fastify.db.getAllUsers() ?? []; @@ -462,6 +492,19 @@ async function onReady(fastify: FastifyInstance) { } }); + socket.on('inviteGame', async (data: string) => { + const clientName: string = clientChat.get(socket.id)?.user || ''; + const profilInvite: ClientProfil = JSON.parse(data) || ''; + // const users: User[] = fastify.db.getAllUsers() ?? []; + + const inviteHtml: string = `invites you to a game ` + getGameLink(); + if (clientName !== null) { + // const testuser: User | null = getUserByName(users, profilInvite.user ?? ''); + // console.log(color.yellow, 'user:', testuser?.name ?? 'Guest'); + sendInvite(inviteHtml, profilInvite); + } + }); + socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload) From 945419427afd3deb507b6250a867b574eb7641c2 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 9 Dec 2025 18:06:04 +0100 Subject: [PATCH 48/66] Added setTimeout to try to correct a small bug --- frontend/src/pages/chat/chat.ts | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 8d75d33..ea68dfd 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -316,22 +316,24 @@ function broadcastMsg (socket: Socket, msgCommand: string[]): void { async function connected(socket: Socket): Promise { try { - const buddies = document.getElementById('div-buddies') as HTMLDivElement; - const loggedIn = await isLoggedIn(); - console.log('%cloggedIn:',color.blue, loggedIn?.name); - let oldUser = localStorage.getItem("oldName") ?? ""; - console.log('%coldUser:',color.yellow, oldUser); - if (loggedIn?.name === undefined) {console.log('');return ;} - oldUser = loggedIn.name ?? ""; - // const res = await client.guestLogin(); - let user = await updateUser(); - console.log('%cUser?name:',color.yellow, user?.name); - localStorage.setItem("oldName", oldUser); - buddies.textContent = ""; - socket.emit('list', { - oldUser: oldUser, - user: user?.name, - }); + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const loggedIn = isLoggedIn(); + console.log('%cloggedIn:',color.blue, loggedIn?.name); + let oldUser = localStorage.getItem("oldName") ?? ""; + console.log('%coldUser:',color.yellow, oldUser); + if (loggedIn?.name === undefined) {console.log('');return ;} + setTimeout(() => { + oldUser = loggedIn.name ?? ""; + }, 0); + // const res = await client.guestLogin(); + let user = await updateUser(); + console.log('%cUser?name:',color.yellow, user?.name); + localStorage.setItem("oldName", oldUser); + buddies.textContent = ""; + socket.emit('list', { + oldUser: oldUser, + user: user?.name, + }); } catch (e) { console.error("Login error:", e); showError('Failed to login: Unknown error'); From 6643d495bdc9f0cd39a60aa41818a09db9ac3692 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 10 Dec 2025 13:10:59 +0100 Subject: [PATCH 49/66] Added Global Message for Next Game - Bug Fixed on Double Displaying Ping Buddies --- frontend/src/chat/chat.css | 27 +++++++++ frontend/src/pages/chat/chat.html | 10 +++- frontend/src/pages/chat/chat.ts | 87 ++++++++++++++++++++------- src/@shared/src/utils/index.ts | 8 +++ src/chat/src/app.ts | 98 +++++++++++++++++++------------ 5 files changed, 170 insertions(+), 60 deletions(-) diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 993c029..b308418 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -230,6 +230,33 @@ div-private { right-12 } + +.popUpMessage { + @apply + bg-white + p-6 rounded-xl + shadow-xl + w-[800px] + h-[100px] + p-[10px] + border-1 + border-black + +} + +.gamePopup { + @apply + fixed + inset-0 + bg-black/50 + flex + justify-center + items-center; +} + + + + .hidden{ display: none; } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index bf06259..87b57be 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,6 +1,7 @@
+

ChatterBox


@@ -26,9 +27,6 @@

Ping Buddies

- Marks
+
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index ea68dfd..fb18ecb 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -87,10 +87,12 @@ function addMessage(text: string) { return ; }; -function clearText() { +function clear(senderSocket: Socket) { const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; if (!chatWindow) return; chatWindow.innerHTML = ""; + // senderSocket.emit('nextGame'); + } function isLoggedIn() { @@ -106,11 +108,11 @@ function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) { -function actionBtnPopUpClear(profil: ClientProfil) { +function actionBtnPopUpClear(profil: ClientProfil, senderSocket: Socket) { setTimeout(() => { const clearTextBtn = document.querySelector("#popup-b-clear"); clearTextBtn?.addEventListener("click", () => { - clearText(); + clear(senderSocket); }); }, 0) }; @@ -240,8 +242,9 @@ async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: }); buddiesElement.addEventListener("dblclick", () => { - console.log("Open profile:", listBuddies); + console.log("Open profile:", listBuddies); getProfil(socket, listBuddies); + sendtextbox.value = ""; }); buddies.appendChild(buddiesElement); @@ -387,12 +390,32 @@ async function openProfilePopup(profil: ClientProfil) {
About: '${profil.text}'
`; - const profilList = document.getElementById("profile-modal") ?? null; + const profilList = document.getElementById("profile-modal") ?? null; if (profilList) profilList.classList.remove("hidden"); // The popup now exists → attach the event } +async function openMessagePopup(message: string) { + + + const modalmessage = document.getElementById("modal-message") ?? null; + if(!message) return + const obj:any = JSON.parse(message); + if (modalmessage) + modalmessage.innerHTML = ` +
+
+
Next Game Message: ${obj.link}
+
`; + const gameMessage = document.getElementById("game-modal") ?? null; + if (gameMessage) + gameMessage.classList.remove("hidden"); + // The popup now exists → attach the event +} + + + function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -470,7 +493,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn socket.on('profilMessage', (profil: ClientProfil) => { openProfilePopup(profil); - actionBtnPopUpClear(profil); + actionBtnPopUpClear(profil, socket); actionBtnPopUpInvite(profil, socket); }); @@ -489,37 +512,39 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn addMessage(message); }) - type Providers = { - name: string, - display_name: string, - icon_url?: string, - color?: { default: string, hover: string }, - }; + //receives broadcast of the next GAME + socket.on('nextGame', (message) => { + openMessagePopup(message); + // addMessage(message); + }) let toggle = false - window.addEventListener("focus", () => { + window.addEventListener("focus", async () => { //nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; - if (window.location.pathname === '/app/chat') { + + setTimeout(() => { connected(socket); - console.log("%cWindow is focused on /chat:" + socket.id, color.green); + }, 0); + if (window.location.pathname === '/app/chat') { + console.log('%cWindow is focused on /chat:' + socket.id, color.green); if (socket.id) { - windowStateVisable(); + await windowStateVisable(); } toggle = true; } }); window.addEventListener("blur", () => { - console.log("%cWindow is not focused on /chat", color.red); + console.log('%cWindow is not focused on /chat', color.red); if (socket.id) windowStateHidden(); toggle = false; }); - // setInterval(async () => { // //connected(socket); - // },10000); // every 10 seco + // },10000); // every 10 sec + socket.on('listBud', async (myBuddies: string) => { const buddies = document.getElementById('div-buddies') as HTMLDivElement; console.log('List buddies connected ', myBuddies); @@ -552,6 +577,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const buddies = document.getElementById('div-buddies') as HTMLDivElement; const bquit = document.getElementById('b-quit') as HTMLDivElement; const systemWindow = document.getElementById('system-box') as HTMLDivElement; + const bnextGame = document.getElementById('b-nextGame') as HTMLDivElement; chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -578,6 +604,18 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (profilList) profilList.classList.add("hidden"); }); + + + + const buttonMessage = document.getElementById("close-modal-message") ?? null; + + if (buttonMessage) + buttonMessage.addEventListener("click", () => { + const gameMessage = document.getElementById("game-modal") ?? null; + if (gameMessage) gameMessage.classList.add("hidden"); + }); + + // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -631,6 +669,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (chatWindow) { chatWindow.innerHTML = ''; } + clear(socket); //DEV testing broadcastGames + }); + + // Dev Game message button + bnextGame?.addEventListener("click", () => { + if (chatWindow) { + socket.emit('nextGame'); + } }); bquit?.addEventListener('click', () => { @@ -652,5 +698,4 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } } }; -addRoute('/chat', handleChat, { bypass_auth: true }); - +addRoute('/chat', handleChat, { bypass_auth: true }); \ No newline at end of file diff --git a/src/@shared/src/utils/index.ts b/src/@shared/src/utils/index.ts index a79cbe9..96d02ad 100644 --- a/src/@shared/src/utils/index.ts +++ b/src/@shared/src/utils/index.ts @@ -142,3 +142,11 @@ export function typeResponse( export function isNullish(v: T | undefined | null): v is null | undefined { return v === null || v === undefined; } + +/** +/* EXPERIMENTAL: how to send a starting game link to chat +**/ +export async function sendGameLinkToChatService(link: string) :Promise { + const payload = { link }; + return JSON.stringify(payload); +} \ No newline at end of file diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 14258e2..e904b15 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -7,6 +7,7 @@ import * as swagger from '@shared/swagger'; import * as utils from '@shared/utils'; import { Server, Socket } from 'socket.io'; import type { User } from '@shared/database/mixin/user'; +import { sendGameLinkToChatService } from '../../@shared/src/utils/index'; // colors for console.log export const color = { @@ -46,7 +47,7 @@ export type ClientProfil = { command: string, destination: string, type: string, - user: string, + user: string, loginName: string, userID: string, text: string, @@ -55,21 +56,28 @@ export type ClientProfil = { SenderName: string, innerHtml?: string, -}; +}; +/** +/* TODO find the description info for profil / or profil game link and return +**/ +function createNextGame() { + return 'The next Game is Starting click here to watch'; +}; -// export type inviteGame = { -// command?: string, -// destination?: string, -// type?: string, -// user?: string, -// loginName?: string, -// userID?: string, -// innerHtml?: string, -// timestamp?: number, -// SenderWindowID?:string, -// SenderName: string -// }; +function setAboutPlayer(about: string): string { + if (!about) { + about = 'Player is good Shape - This is a default description'; + } + return about; +}; + +function setGameLink(link: string): string { + if (!link) { + link = 'Click me'; + } + return link; +}; const clientChat = new Map(); @@ -118,6 +126,7 @@ declare module 'fastify' { profilMessage: (data: ClientProfil) => void; inviteGame: (data: ClientProfil) => void; privMessageCopy: (msg: string) => void; + nextGame: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -189,6 +198,28 @@ async function onReady(fastify: FastifyInstance) { }); } + + async function broadcastNextGame(gameLink?: Promise) { + + const link = gameLink ? await gameLink : undefined; + const sockets = await fastify.io.fetchSockets(); + // fastify.io.fetchSockets().then((sockets) => { + for (const socket of sockets) { + // Skip sender's own socket + const clientInfo = clientChat.get(socket.id); + if (!clientInfo?.user) { + console.log(color.yellow, `Skipping socket ${socket.id} (no user found)`); + continue; + } + // Emit structured JSON object + if (link) { + socket.emit('nextGame', link); + } + // Debug logs + // console.log(color.green, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`); + } + }; + // function formatTimestamp(ms: number) { // const d = new Date(ms); // return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); @@ -198,21 +229,9 @@ async function onReady(fastify: FastifyInstance) { return users.find(user => user.name === name) || null; } - ///TODO find the description info for profil / or profil game link and return - function getAboutPlayer( ): string{ - const description = 'Player is good Shape this needs to be replaced by a bd.function()'; - return description; - } - - function getGameLink(): string{ - const link = `Click me`; - return link; - } - - // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... async function getProfil(user: string, socket: Socket): Promise { - + let clientProfil!: ClientProfil; const users: User[] = fastify.db.getAllUsers() ?? []; const allUsers: User | null = getUserByName(users, user); @@ -220,20 +239,20 @@ async function onReady(fastify: FastifyInstance) { if (user === allUsers?.name) { console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`); - clientProfil = + clientProfil = { command: 'getProfil', destination: 'profilMsg', type: 'chat' as const, - user: `${allUsers.name}`, + user: `${allUsers.name}`, loginName: `${allUsers?.login ?? 'Guest'}`, userID: `${allUsers?.id ?? ''}`, - text: getAboutPlayer(), + text: setAboutPlayer(''), timestamp: Date.now(), SenderWindowID: socket.id, SenderName: '', innerHtml: '', - }; + }; } return clientProfil; }; @@ -250,10 +269,10 @@ async function onReady(fastify: FastifyInstance) { function sendInvite(innerHtml: string, data: ClientProfil) { fastify.io.fetchSockets().then((sockets) => { - + let targetSocket; for (const socket of sockets) { - let clientInfo: string = clientChat.get(socket.id)?.user || ''; + const clientInfo: string = clientChat.get(socket.id)?.user || ''; if (clientInfo === data.user) { console.log(color.yellow, 'FOUND:', data.user); targetSocket = socket || ''; @@ -261,7 +280,7 @@ async function onReady(fastify: FastifyInstance) { } } data.innerHtml = innerHtml ?? ''; - if (targetSocket) { + if (targetSocket) { targetSocket.emit('inviteGame', data); } }); @@ -333,6 +352,13 @@ async function onReady(fastify: FastifyInstance) { console.log('testend received from client socket id:', sock_id_cl); }); + + socket.on('nextGame', () => { + const link = createNextGame(); + const game: Promise = sendGameLinkToChatService(link); + broadcastNextGame(game); + }); + socket.on('list', (object) => { const userFromFrontend = object || null; @@ -496,8 +522,8 @@ async function onReady(fastify: FastifyInstance) { const clientName: string = clientChat.get(socket.id)?.user || ''; const profilInvite: ClientProfil = JSON.parse(data) || ''; // const users: User[] = fastify.db.getAllUsers() ?? []; - - const inviteHtml: string = `invites you to a game ` + getGameLink(); + + const inviteHtml: string = 'invites you to a game ' + setGameLink(''); if (clientName !== null) { // const testuser: User | null = getUserByName(users, profilInvite.user ?? ''); // console.log(color.yellow, 'user:', testuser?.name ?? 'Guest'); From 79c7edb30fae4dfce2446848e4a839384d245827 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Mon, 1 Dec 2025 15:29:42 +0100 Subject: [PATCH 50/66] chore(deps): Removed unused deps and updated lockfiles --- .envrc | 2 +- frontend/package.json | 3 +- frontend/pnpm-lock.yaml | 37 +- src/@shared/package.json | 2 +- src/auth/package.json | 4 +- src/chat/package.json | 4 +- src/icons/package.json | 4 +- src/package.json | 13 +- src/pnpm-lock.yaml | 1168 ++++++-------------------------------- src/pnpm-workspace.yaml | 8 +- src/user/package.json | 4 +- 11 files changed, 190 insertions(+), 1059 deletions(-) diff --git a/.envrc b/.envrc index 3550a30..24e198b 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake +use flake "path:$(realpath -m .)" diff --git a/frontend/package.json b/frontend/package.json index 04bd252..b0fc5a2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,13 +11,12 @@ "devDependencies": { "@types/js-cookie": "^3.0.6", "typescript": "~5.9.3", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4" }, "dependencies": { "@tailwindcss/vite": "^4.1.17", "js-cookie": "^3.0.5", - "openapi-fetch": "^0.15.0", "socket.io-client": "^4.8.1", "tailwindcss": "^4.1.17" } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 6addf30..ac3aea5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,13 +10,10 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)) + version: 4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)) js-cookie: specifier: ^3.0.5 version: 3.0.5 - openapi-fetch: - specifier: ^0.15.0 - version: 0.15.0 socket.io-client: specifier: ^4.8.1 version: 4.8.1 @@ -31,11 +28,11 @@ importers: specifier: ~5.9.3 version: 5.9.3 vite: - specifier: ^7.2.6 - version: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) + specifier: ^7.2.7 + version: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)) packages: @@ -567,12 +564,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - openapi-fetch@0.15.0: - resolution: {integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==} - - openapi-typescript-helpers@0.0.15: - resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -635,8 +626,8 @@ packages: vite: optional: true - vite@7.2.6: - resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} + vite@7.2.7: + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -919,12 +910,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) '@types/estree@1.0.8': {} @@ -1060,12 +1051,6 @@ snapshots: nanoid@3.3.11: {} - openapi-fetch@0.15.0: - dependencies: - openapi-typescript-helpers: 0.0.15 - - openapi-typescript-helpers@0.0.15: {} - picocolors@1.1.1: {} picomatch@4.0.3: {} @@ -1139,18 +1124,18 @@ snapshots: typescript@5.9.3: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.6(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.6(jiti@2.6.1)(lightningcss@1.30.2): + vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) diff --git a/src/@shared/package.json b/src/@shared/package.json index d563f63..f3309d6 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -25,6 +25,6 @@ }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", - "@types/node": "^22.19.1" + "@types/node": "^22.19.2" } } diff --git a/src/auth/package.json b/src/auth/package.json index 81d3afe..c95aa25 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -30,9 +30,9 @@ "typebox": "^1.0.61" }, "devDependencies": { - "@types/node": "^22.19.1", + "@types/node": "^22.19.2", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/chat/package.json b/src/chat/package.json index 1e61398..12a2b6c 100644 --- a/src/chat/package.json +++ b/src/chat/package.json @@ -30,9 +30,9 @@ "typebox": "^1.0.61" }, "devDependencies": { - "@types/node": "^22.19.1", + "@types/node": "^22.19.2", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/icons/package.json b/src/icons/package.json index 3bef3d5..bce9ed7 100644 --- a/src/icons/package.json +++ b/src/icons/package.json @@ -29,9 +29,9 @@ "sharp": "^0.34.5" }, "devDependencies": { - "@types/node": "^22.19.1", + "@types/node": "^22.19.2", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/src/package.json b/src/package.json index 45b7a6d..d1b8521 100644 --- a/src/package.json +++ b/src/package.json @@ -23,20 +23,17 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@openapitools/openapi-generator-cli": "^2.25.2", - "@typescript-eslint/eslint-plugin": "^8.48.1", - "@typescript-eslint/parser": "^8.48.1", + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", "eslint": "^9.39.1", "husky": "^9.1.7", "lint-staged": "^16.2.7", - "openapi-generator-cli": "^1.0.0", - "openapi-typescript": "^7.10.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.48.1", - "vite": "^7.2.6" + "typescript-eslint": "^8.49.0", + "vite": "^7.2.7" }, "dependencies": { - "@redocly/cli": "^2.12.3", + "@redocly/cli": "^2.12.5", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index a50dc94..cab5f92 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.12.3 - version: 2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.5 + version: 2.12.5(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -18,15 +18,12 @@ importers: '@eslint/js': specifier: ^9.39.1 version: 9.39.1 - '@openapitools/openapi-generator-cli': - specifier: ^2.25.2 - version: 2.25.2(@types/node@24.10.1) '@typescript-eslint/eslint-plugin': - specifier: ^8.48.1 - version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) eslint: specifier: ^9.39.1 version: 9.39.1 @@ -36,21 +33,15 @@ importers: lint-staged: specifier: ^16.2.7 version: 16.2.7 - openapi-generator-cli: - specifier: ^1.0.0 - version: 1.0.0 - openapi-typescript: - specifier: ^7.10.1 - version: 7.10.1(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@24.10.1)(yaml@2.8.2) + specifier: ^7.2.7 + version: 7.2.7(@types/node@24.10.2)(yaml@2.8.2) '@shared': dependencies: @@ -98,8 +89,8 @@ importers: specifier: ^7.6.13 version: 7.6.13 '@types/node': - specifier: ^22.19.1 - version: 22.19.1 + specifier: ^22.19.2 + version: 22.19.2 auth: dependencies: @@ -135,17 +126,17 @@ importers: version: 1.0.61 devDependencies: '@types/node': - specifier: ^22.19.1 - version: 22.19.1 + specifier: ^22.19.2 + version: 22.19.2 rollup-plugin-node-externals: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) + specifier: ^7.2.7 + version: 7.2.7(@types/node@22.19.2)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2)) chat: dependencies: @@ -181,17 +172,17 @@ importers: version: 1.0.61 devDependencies: '@types/node': - specifier: ^22.19.1 - version: 22.19.1 + specifier: ^22.19.2 + version: 22.19.2 rollup-plugin-node-externals: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) + specifier: ^7.2.7 + version: 7.2.7(@types/node@22.19.2)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2)) icons: dependencies: @@ -227,17 +218,17 @@ importers: version: 0.34.5 devDependencies: '@types/node': - specifier: ^22.19.1 - version: 22.19.1 + specifier: ^22.19.2 + version: 22.19.2 rollup-plugin-node-externals: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) + specifier: ^7.2.7 + version: 7.2.7(@types/node@22.19.2)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2)) user: dependencies: @@ -270,17 +261,17 @@ importers: version: 1.0.61 devDependencies: '@types/node': - specifier: ^22.19.1 - version: 22.19.1 + specifier: ^22.19.2 + version: 22.19.2 rollup-plugin-node-externals: specifier: ^8.1.2 version: 8.1.2(rollup@4.53.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) + specifier: ^7.2.7 + version: 7.2.7(@types/node@22.19.2)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2)) packages: @@ -296,9 +287,6 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@borewit/text-codec@0.1.1': - resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} - '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -749,15 +737,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -770,71 +749,14 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} - '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} - '@nestjs/axios@4.0.1': - resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==} - peerDependencies: - '@nestjs/common': ^10.0.0 || ^11.0.0 - axios: ^1.3.1 - rxjs: ^7.0.0 - - '@nestjs/common@11.1.9': - resolution: {integrity: sha512-zDntUTReRbAThIfSp3dQZ9kKqI+LjgLp5YZN5c1bgNRDuoeLySAoZg46Bg1a+uV8TMgIRziHocglKGNzr6l+bQ==} - peerDependencies: - class-transformer: '>=0.4.1' - class-validator: '>=0.13.2' - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/core@11.1.9': - resolution: {integrity: sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw==} - engines: {node: '>= 20'} - peerDependencies: - '@nestjs/common': ^11.0.0 - '@nestjs/microservices': ^11.0.0 - '@nestjs/platform-express': ^11.0.0 - '@nestjs/websockets': ^11.0.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} - '@nuxt/opencollective@0.4.1': - resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} - engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} - hasBin: true - - '@nuxtjs/opencollective@0.3.2': - resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - '@openapitools/openapi-generator-cli@2.25.2': - resolution: {integrity: sha512-TXElbW1NXCy0EECXiO5AD2ZzT1dmaCs41Z8t3pBUGaJf8zgF/Lm0P6GRhVEpw29iHBNjZcy8nrgQ1acUfuCdng==} - engines: {node: '>=16'} - hasBin: true - '@opentelemetry/api-logs@0.202.0': resolution: {integrity: sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw==} engines: {node: '>=8.0.0'} @@ -943,27 +865,27 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.12.3': - resolution: {integrity: sha512-1SDW551scNdb4HmNpzyUf4gjsK89KkRUeXF91VVMRkQ5+lFEq1Nj259jN1M25uOd/cg1QjKE3kIbnN1dxPa3ng==} + '@redocly/cli@2.12.5': + resolution: {integrity: sha512-/kIIKHRpsIu/QxvLzsuxsFFbMJqQmCcbcZUSkNhbeJdYkB+3y5R6xpsLOlcaC33wJePcbDg2v3VYfKEhbU6rtA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.40.0': - resolution: {integrity: sha512-MZQZs7QEGnue3rVN9Q9QvDbcGjesxbpKXUvDeckS69R1xjtgsnT9B39VA25zmwSJtgUeA9ST+sMf9GxIqixNbw==} + '@redocly/config@0.41.0': + resolution: {integrity: sha512-8yJ2e+ex8KVF25zijdpDbAEjyubk7NLfHsLI8h0MUnLEo2iEg6rTCDT9Qw71XDqd5UlXvfJb0Z0h6dd+Y6pWLw==} - '@redocly/openapi-core@1.34.5': - resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} + '@redocly/openapi-core@1.34.6': + resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.12.3': - resolution: {integrity: sha512-3gdSRftIeUbzXvwDi/tBjO0uj9PzR0XzbWjNwuu3HlVXJ1ElB+K31AnzQ2iA6mjIHq9uvmLRXAs9MsP/0Hbzug==} + '@redocly/openapi-core@2.12.5': + resolution: {integrity: sha512-PlzsfOgPUWg4OmyznsIWQ6EnfZK6KSOn2MG4sZS3DY9cHgno5YywNy8zFQWHADXLqR7hTw2M7VvbiMKVJxzyiA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.12.3': - resolution: {integrity: sha512-ZYqrLBlRVVHwgPawOjo94sKmeuuien77xtkXluTa6+y/wkQ8c5oYY7OqWbasMv0IoxSPehwVMa0AL0OCQP3uCQ==} + '@redocly/respect-core@2.12.5': + resolution: {integrity: sha512-c0Tj+Yqd48uCD+fo0WtNeHeVIBkSWNpyT+L3ViAyMXxWD6HiMaA7JTb+IXJXhWdcUrVwd4l1gMuduSyvzFX2gg==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -1082,16 +1004,6 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@tokenizer/inflate@0.3.1': - resolution: {integrity: sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==} - engines: {node: '>=18'} - - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/bcrypt@6.0.0': resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==} @@ -1107,11 +1019,11 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.19.1': - resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + '@types/node@22.19.2': + resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} '@types/stylis@4.2.5': resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} @@ -1119,63 +1031,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} abort-controller@3.0.0: @@ -1217,14 +1129,6 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-escapes@7.2.0: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} @@ -1255,10 +1159,6 @@ packages: asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1269,9 +1169,6 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} - axios@1.13.2: - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1282,10 +1179,6 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - bcrypt@6.0.0: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} @@ -1347,12 +1240,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - change-case@5.4.4: - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} @@ -1370,37 +1257,17 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-truncate@5.1.1: resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - close-with-grace@2.3.0: resolution: {integrity: sha512-38BS9BuqAml6XFIlSWQcj3eivE05yFV6cJDuYoNGiHrE+h9ud1JtMJIVKXdLWa2Uo2Xt7q/GYczOesEchvBEsw==} @@ -1429,38 +1296,15 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - commist@3.2.0: resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} - compare-versions@6.1.1: - resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} - engines: {node: '>=18'} - hasBin: true - confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - console.table@0.10.0: - resolution: {integrity: sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==} - engines: {node: '> 0.10'} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1501,10 +1345,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -1540,13 +1380,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1563,8 +1396,8 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dompurify@3.3.0: - resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} @@ -1584,9 +1417,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - easy-table@1.1.0: - resolution: {integrity: sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==} - ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -1645,19 +1475,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1684,11 +1505,6 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -1716,8 +1532,8 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - fast-copy@4.0.0: - resolution: {integrity: sha512-/oA0gx1xyXE9R2YlV4FXwZJXngFdm9Du0zN8FhY38jnLkhp1u35h6bCyKgRhlsA6C9I+1vfXE4KISdt7xc6M9w==} + fast-copy@4.0.1: + resolution: {integrity: sha512-+uUOQlhsaswsizHFmEFAQhB3lSiQ+lisxl50N6ZP0wywlZeWsIESxSi9ftPEps8UGfiBzyYP7x27zA674WUvXw==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -1783,21 +1599,10 @@ packages: picomatch: optional: true - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-type@21.1.0: - resolution: {integrity: sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==} - engines: {node: '>=20'} - file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1824,15 +1629,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - foreach@2.0.6: resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} @@ -1851,10 +1647,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} - engines: {node: '>=14.14'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1883,10 +1675,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-uri@6.0.5: - resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} - engines: {node: '>= 14'} - github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -1903,10 +1691,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - glob@13.0.0: - resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} - engines: {node: 20 || >=22} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1918,12 +1702,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -1952,10 +1730,6 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - http2-client@1.3.5: resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} @@ -1991,24 +1765,12 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - index-to-position@1.2.0: - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} - engines: {node: '>=18'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inquirer@8.2.7: - resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} - engines: {node: '>=12.0.0'} - - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - ipaddr.js@2.3.0: resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} @@ -2038,18 +1800,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -2057,10 +1811,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iterare@1.2.1: - resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} - engines: {node: '>=6'} - jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} @@ -2110,9 +1860,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonpath-rfc9535@1.3.0: resolution: {integrity: sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==} engines: {node: '>=20'} @@ -2144,10 +1891,6 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} - load-esm@1.0.3: - resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} - engines: {node: '>=13.2.0'} - locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -2159,13 +1902,6 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -2181,10 +1917,6 @@ packages: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -2232,10 +1964,6 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -2307,9 +2035,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -2332,10 +2057,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - node-abi@3.85.0: resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} engines: {node: '>=10'} @@ -2398,38 +2119,20 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - openapi-generator-cli@1.0.0: - resolution: {integrity: sha512-gJ6YXQAkmoTYZ6kPG3rX9lHvVTh/bnWxfr6pDxjUfx+vSoE+JsuR4LDw+MrgH/7ezsJRFaljR66k0AlOXDW/eQ==} - hasBin: true - openapi-sampler@1.6.2: resolution: {integrity: sha512-NyKGiFKfSWAZr4srD/5WDhInOWDhfml32h/FKUqLpEwKJt0kG0LGUU0MdyNkKrVGuJnw6DuPWq/sHCwAMpiRxg==} openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - openapi-typescript@7.10.1: - resolution: {integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==} - hasBin: true - peerDependencies: - typescript: ^5.x - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - otp@1.1.2: resolution: {integrity: sha512-VoueTSCMNTCYyHMGkNfndkFSXNv+iyEJ8D1/zD5G0Rd/QUHWozySmezRWKdVAhxcvmL3e5qwhEJBH/JF9MyE+g==} @@ -2456,14 +2159,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2471,10 +2166,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} - path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2494,9 +2185,6 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - perfect-scrollbar@1.5.6: resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==} @@ -2582,13 +2270,6 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -2656,9 +2337,6 @@ packages: react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 styled-components: ^4.1.1 || ^5.1.1 || ^6.0.5 - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - reftools@1.1.9: resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} @@ -2678,10 +2356,6 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -2708,13 +2382,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2763,10 +2430,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} - should-equal@2.0.0: resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==} @@ -2785,9 +2448,6 @@ packages: should@13.2.3: resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2809,10 +2469,6 @@ packages: resolution: {integrity: sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==} engines: {node: '>=8.0.0'} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -2824,14 +2480,6 @@ packages: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} @@ -2909,10 +2557,6 @@ packages: strnum@1.1.2: resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} - strtok3@10.3.4: - resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} - engines: {node: '>=18'} - styled-components@6.1.19: resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} engines: {node: '>= 16'} @@ -2923,18 +2567,10 @@ packages: stylis@4.3.2: resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - swagger2openapi@7.0.8: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true @@ -2949,9 +2585,6 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2971,17 +2604,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - token-types@6.1.1: - resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} - engines: {node: '>=14.16'} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - ts-algebra@1.2.2: resolution: {integrity: sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==} @@ -3014,14 +2639,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -3029,8 +2646,8 @@ packages: typebox@1.0.61: resolution: {integrity: sha512-5KeeL5QoPBoYm8Z7tGR1Pw9FjWA75MLhVuiSMCRgtgTg/d2+kTvolFddhOUua9FxpIaqXznFPZcc3sl6cEpafw==} - typescript-eslint@8.48.1: - resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} + typescript-eslint@8.49.0: + resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3046,14 +2663,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} - - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} - ulid@3.0.2: resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} hasBin: true @@ -3068,10 +2677,6 @@ packages: resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} engines: {node: '>=18.17'} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -3106,8 +2711,8 @@ packages: vite: optional: true - vite@7.2.6: - resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} + vite@7.2.7: + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3149,9 +2754,6 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -3170,10 +2772,6 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3249,10 +2847,6 @@ packages: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - yargs-parser@22.0.0: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} @@ -3261,10 +2855,6 @@ packages: resolution: {integrity: sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==} engines: {node: '>=12'} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3281,8 +2871,6 @@ snapshots: '@babel/runtime@7.28.4': {} - '@borewit/text-codec@0.1.1': {} - '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 @@ -3384,7 +2972,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3400,7 +2988,7 @@ snapshots: '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -3660,13 +3248,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/external-editor@1.0.3(@types/node@24.10.1)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.0 - optionalDependencies: - '@types/node': 24.10.1 - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -3682,84 +3263,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@lukeed/csprng@1.1.0': {} - '@lukeed/ms@2.0.2': {} - '@nestjs/axios@4.0.1(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)': - dependencies: - '@nestjs/common': 11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2) - axios: 1.13.2 - rxjs: 7.8.2 - - '@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2)': - dependencies: - file-type: 21.1.0 - iterare: 1.2.1 - load-esm: 1.0.3 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - transitivePeerDependencies: - - supports-color - - '@nestjs/core@11.1.9(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': - dependencies: - '@nestjs/common': 11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nuxt/opencollective': 0.4.1 - fast-safe-stringify: 2.1.1 - iterare: 1.2.1 - path-to-regexp: 8.3.0 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - '@noble/hashes@1.8.0': {} - '@nuxt/opencollective@0.4.1': - dependencies: - consola: 3.4.2 - - '@nuxtjs/opencollective@0.3.2': - dependencies: - chalk: 4.1.2 - consola: 2.15.3 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - '@openapitools/openapi-generator-cli@2.25.2(@types/node@24.10.1)': - dependencies: - '@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2) - '@nestjs/common': 11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nuxtjs/opencollective': 0.3.2 - axios: 1.13.2 - chalk: 4.1.2 - commander: 8.3.0 - compare-versions: 6.1.1 - concurrently: 9.2.1 - console.table: 0.10.0 - fs-extra: 11.3.2 - glob: 13.0.0 - inquirer: 8.2.7(@types/node@24.10.1) - proxy-agent: 6.5.0 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - transitivePeerDependencies: - - '@nestjs/microservices' - - '@nestjs/platform-express' - - '@nestjs/websockets' - - '@types/node' - - class-transformer - - class-validator - - debug - - encoding - - supports-color - '@opentelemetry/api-logs@0.202.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -3868,14 +3375,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.5(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.12.3(ajv@8.17.1) - '@redocly/respect-core': 2.12.3(ajv@8.17.1) + '@redocly/openapi-core': 2.12.5(ajv@8.17.1) + '@redocly/respect-core': 2.12.5(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3884,7 +3391,7 @@ snapshots: form-data: 4.0.5 glob: 11.1.0 handlebars: 4.7.8 - https-proxy-agent: 7.0.6(supports-color@10.2.2) + https-proxy-agent: 7.0.6 mobx: 6.15.0 pluralize: 8.0.0 react: 19.2.1 @@ -3909,16 +3416,16 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.40.0': + '@redocly/config@0.41.0': dependencies: json-schema-to-ts: 2.7.2 - '@redocly/openapi-core@1.34.5(supports-color@10.2.2)': + '@redocly/openapi-core@1.34.6': dependencies: '@redocly/ajv': 8.17.1 '@redocly/config': 0.22.2 colorette: 1.4.0 - https-proxy-agent: 7.0.6(supports-color@10.2.2) + https-proxy-agent: 7.0.6 js-levenshtein: 1.1.6 js-yaml: 4.1.1 minimatch: 5.1.6 @@ -3927,10 +3434,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.12.3(ajv@8.17.1)': + '@redocly/openapi-core@2.12.5(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 - '@redocly/config': 0.40.0 + '@redocly/config': 0.41.0 ajv-formats: 3.0.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 @@ -3941,12 +3448,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.12.3(ajv@8.17.1)': + '@redocly/respect-core@2.12.5(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.12.3(ajv@8.17.1) + '@redocly/openapi-core': 2.12.5(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -4026,39 +3533,27 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@tokenizer/inflate@0.3.1': - dependencies: - debug: 4.4.3(supports-color@10.2.2) - fflate: 0.8.2 - token-types: 6.1.1 - transitivePeerDependencies: - - supports-color - - '@tokenizer/token@0.3.0': {} - - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/bcrypt@6.0.0': dependencies: - '@types/node': 22.19.1 + '@types/node': 22.19.2 '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 22.19.1 + '@types/node': 22.19.2 '@types/cors@2.8.19': dependencies: - '@types/node': 22.19.1 + '@types/node': 22.19.2 '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} - '@types/node@22.19.1': + '@types/node@22.19.2': dependencies: undici-types: 6.21.0 - '@types/node@24.10.1': + '@types/node@24.10.2': dependencies: undici-types: 7.16.0 @@ -4067,16 +3562,15 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.1 - graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -4084,57 +3578,57 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 - debug: 4.4.3(supports-color@10.2.2) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - debug: 4.4.3(supports-color@10.2.2) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - debug: 4.4.3(supports-color@10.2.2) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 - debug: 4.4.3(supports-color@10.2.2) + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -4143,20 +3637,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 abort-controller@3.0.0: @@ -4196,12 +3690,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ansi-colors@4.1.3: {} - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-escapes@7.2.0: dependencies: environment: 1.1.0 @@ -4230,10 +3718,6 @@ snapshots: minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -4243,22 +3727,12 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.19.1 - axios@1.13.2: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - balanced-match@1.0.2: {} base64-js@1.5.1: {} base64id@2.0.0: {} - basic-ftp@5.0.5: {} - bcrypt@6.0.0: dependencies: node-addon-api: 8.5.0 @@ -4328,10 +3802,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - change-case@5.4.4: {} - - chardet@2.1.1: {} - charenc@0.0.2: {} chokidar@3.6.0: @@ -4354,37 +3824,21 @@ snapshots: classnames@2.5.1: {} - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 - cli-spinners@2.9.2: {} - cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 string-width: 8.1.0 - cli-width@3.0.0: {} - cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - clone@1.0.4: {} - close-with-grace@2.3.0: {} clsx@2.1.1: {} @@ -4405,33 +3859,12 @@ snapshots: commander@14.0.2: {} - commander@8.3.0: {} - commist@3.2.0: {} - compare-versions@6.1.1: {} - concat-map@0.0.1: {} - concurrently@9.2.1: - dependencies: - chalk: 4.1.2 - rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 - confbox@0.2.2: {} - consola@2.15.3: {} - - consola@3.4.2: {} - - console.table@0.10.0: - dependencies: - easy-table: 1.1.0 - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -4467,19 +3900,15 @@ snapshots: csstype@3.1.3: {} - data-uri-to-buffer@6.0.2: {} - dateformat@4.6.3: {} debug@4.3.7: dependencies: ms: 2.1.3 - debug@4.4.3(supports-color@10.2.2): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 decko@1.2.0: {} @@ -4491,16 +3920,6 @@ snapshots: deep-is@0.1.4: {} - defaults@1.0.4: - dependencies: - clone: 1.0.4 - - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -4509,7 +3928,7 @@ snapshots: detect-libc@2.1.2: {} - dompurify@3.3.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -4532,10 +3951,6 @@ snapshots: eastasianwidth@0.2.0: {} - easy-table@1.1.0: - optionalDependencies: - wcwidth: 1.0.1 - ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -4555,7 +3970,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.19 - '@types/node': 22.19.1 + '@types/node': 22.19.2 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -4620,18 +4035,8 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -4658,7 +4063,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -4686,8 +4091,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 - esprima@4.0.1: {} - esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -4706,7 +4109,7 @@ snapshots: expand-template@2.0.3: {} - fast-copy@4.0.0: {} + fast-copy@4.0.1: {} fast-decode-uri-component@1.0.1: {} @@ -4805,25 +4208,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fflate@0.8.2: {} - - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 - file-type@21.1.0: - dependencies: - '@tokenizer/inflate': 0.3.1 - strtok3: 10.3.4 - token-types: 6.1.1 - uint8array-extras: 1.5.0 - transitivePeerDependencies: - - supports-color - file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -4852,8 +4240,6 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.11: {} - foreach@2.0.6: {} foreground-child@3.3.1: @@ -4873,12 +4259,6 @@ snapshots: fs-constants@1.0.0: {} - fs-extra@11.3.2: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - fsevents@2.3.3: optional: true @@ -4913,14 +4293,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-uri@6.0.5: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -4940,22 +4312,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.1 - glob@13.0.0: - dependencies: - minimatch: 10.1.1 - minipass: 7.1.2 - path-scurry: 2.0.1 - globals@14.0.0: {} globrex@0.1.2: {} gopd@1.2.0: {} - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -4987,19 +4349,12 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - http2-client@1.3.5: {} - https-proxy-agent@7.0.6(supports-color@10.2.2): + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5022,34 +4377,10 @@ snapshots: imurmurhash@0.1.4: {} - index-to-position@1.2.0: {} - inherits@2.0.4: {} ini@1.3.8: {} - inquirer@8.2.7(@types/node@24.10.1): - dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - transitivePeerDependencies: - - '@types/node' - - ip-address@10.1.0: {} - ipaddr.js@2.3.0: {} is-binary-path@2.1.0: @@ -5070,18 +4401,12 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-interactive@1.0.0: {} - is-number@7.0.0: {} - is-unicode-supported@0.1.0: {} - isbinaryfile@4.0.10: {} isexe@2.0.0: {} - iterare@1.2.1: {} - jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -5118,7 +4443,7 @@ snapshots: json-schema-resolver@3.0.0: dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 fast-uri: 3.1.0 rfdc: 1.4.1 transitivePeerDependencies: @@ -5136,12 +4461,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsonpath-rfc9535@1.3.0: {} jsonpointer@5.0.1: {} @@ -5182,8 +4501,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 - load-esm@1.0.3: {} - locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -5195,13 +4512,6 @@ snapshots: lodash.merge@4.6.2: {} - lodash@4.17.21: {} - - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - log-update@6.1.0: dependencies: ansi-escapes: 7.2.0 @@ -5218,8 +4528,6 @@ snapshots: lru-cache@11.2.4: {} - lru-cache@7.18.3: {} - lunr@2.3.9: {} makeerror@1.0.12: @@ -5253,8 +4561,6 @@ snapshots: mime@3.0.0: {} - mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} mimic-response@3.1.0: {} @@ -5307,8 +4613,6 @@ snapshots: ms@2.1.3: {} - mute-stream@0.0.8: {} - nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -5321,8 +4625,6 @@ snapshots: neo-async@2.6.2: {} - netmask@2.0.2: {} - node-abi@3.85.0: dependencies: semver: 7.7.3 @@ -5386,16 +4688,10 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@7.0.0: dependencies: mimic-function: 5.0.1 - openapi-generator-cli@1.0.0: {} - openapi-sampler@1.6.2: dependencies: '@types/json-schema': 7.0.15 @@ -5404,16 +4700,6 @@ snapshots: openapi-types@12.1.3: {} - openapi-typescript@7.10.1(typescript@5.9.3): - dependencies: - '@redocly/openapi-core': 1.34.5(supports-color@10.2.2) - ansi-colors: 4.1.3 - change-case: 5.4.4 - parse-json: 8.3.0 - supports-color: 10.2.2 - typescript: 5.9.3 - yargs-parser: 21.1.1 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5423,18 +4709,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - otp@1.1.2: dependencies: sha1: 1.1.1 @@ -5459,36 +4733,12 @@ snapshots: p-try@2.2.0: {} - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) - get-uri: 6.0.5 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6(supports-color@10.2.2) - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - package-json-from-dist@1.0.1: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.27.1 - index-to-position: 1.2.0 - type-fest: 4.41.0 - path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -5502,8 +4752,6 @@ snapshots: lru-cache: 11.2.4 minipass: 7.1.2 - path-to-regexp@8.3.0: {} - perfect-scrollbar@1.5.6: {} picocolors@1.1.1: {} @@ -5526,7 +4774,7 @@ snapshots: dependencies: colorette: 2.0.20 dateformat: 4.6.3 - fast-copy: 4.0.0 + fast-copy: 4.0.1 fast-safe-stringify: 2.1.1 help-me: 5.0.0 joycon: 3.1.1 @@ -5619,24 +4867,9 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 long: 5.3.2 - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6(supports-color@10.2.2) - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - proxy-from-env@1.1.0: {} - pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -5697,11 +4930,11 @@ snapshots: redoc@2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)): dependencies: - '@redocly/openapi-core': 1.34.5(supports-color@10.2.2) + '@redocly/openapi-core': 1.34.6 classnames: 2.5.1 core-js: 3.47.0 decko: 1.2.0 - dompurify: 3.3.0 + dompurify: 3.3.1 eventemitter3: 5.0.1 json-pointer: 0.6.2 lunr: 2.3.9 @@ -5728,8 +4961,6 @@ snapshots: - react-native - supports-color - reflect-metadata@0.2.2: {} - reftools@1.1.9: {} require-directory@2.1.1: {} @@ -5740,11 +4971,6 @@ snapshots: resolve-from@5.0.0: {} - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -5788,12 +5014,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 - run-async@2.4.1: {} - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - safe-buffer@5.2.1: {} safe-regex2@5.0.0: @@ -5858,8 +5078,6 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.3: {} - should-equal@2.0.0: dependencies: should-type: 1.4.0 @@ -5886,8 +5104,6 @@ snapshots: should-type-adaptors: 1.1.0 should-util: 1.0.1 - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -5900,7 +5116,7 @@ snapshots: simple-websocket@9.1.0: dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.2 @@ -5917,8 +5133,6 @@ snapshots: slugify@1.4.7: {} - smart-buffer@4.2.0: {} - socket.io-adapter@2.5.5: dependencies: debug: 4.3.7 @@ -5949,19 +5163,6 @@ snapshots: - supports-color - utf-8-validate - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 - sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -6035,10 +5236,6 @@ snapshots: strnum@1.1.2: {} - strtok3@10.3.4: - dependencies: - '@tokenizer/token': 0.3.0 - styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@emotion/is-prop-valid': 1.2.2 @@ -6055,16 +5252,10 @@ snapshots: stylis@4.3.2: {} - supports-color@10.2.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - swagger2openapi@7.0.8: dependencies: call-me-maybe: 1.0.2 @@ -6100,8 +5291,6 @@ snapshots: dependencies: real-require: 0.2.0 - through@2.3.8: {} - tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -6117,16 +5306,8 @@ snapshots: toidentifier@1.0.1: {} - token-types@6.1.1: - dependencies: - '@borewit/text-codec': 0.1.1 - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - tr46@0.0.3: {} - tree-kill@1.2.2: {} - ts-algebra@1.2.2: {} ts-api-utils@2.1.0(typescript@5.9.3): @@ -6139,7 +5320,8 @@ snapshots: tslib@2.6.2: {} - tslib@2.8.1: {} + tslib@2.8.1: + optional: true tunnel-agent@0.6.0: dependencies: @@ -6149,10 +5331,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.21.3: {} - - type-fest@4.41.0: {} - type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -6161,12 +5339,12 @@ snapshots: typebox@1.0.61: {} - typescript-eslint@8.48.1(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.49.0(eslint@9.39.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: @@ -6177,12 +5355,6 @@ snapshots: uglify-js@3.19.3: optional: true - uid@2.0.2: - dependencies: - '@lukeed/csprng': 1.1.0 - - uint8array-extras@1.5.0: {} - ulid@3.0.2: {} undici-types@6.21.0: {} @@ -6191,8 +5363,6 @@ snapshots: undici@6.22.0: {} - universalify@2.0.1: {} - unpipe@1.0.0: {} uri-js@4.4.1: @@ -6211,18 +5381,18 @@ snapshots: vary@1.1.2: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2)): dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.6(@types/node@22.19.1)(yaml@2.8.2) + vite: 7.2.7(@types/node@22.19.2)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.6(@types/node@22.19.1)(yaml@2.8.2): + vite@7.2.7(@types/node@22.19.2)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6231,11 +5401,11 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.19.1 + '@types/node': 22.19.2 fsevents: 2.3.3 yaml: 2.8.2 - vite@7.2.6(@types/node@24.10.1)(yaml@2.8.2): + vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6244,7 +5414,7 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 fsevents: 2.3.3 yaml: 2.8.2 @@ -6252,10 +5422,6 @@ snapshots: dependencies: makeerror: 1.0.12 - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -6271,12 +5437,6 @@ snapshots: wordwrap@1.0.0: {} - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6315,8 +5475,6 @@ snapshots: yargs-parser@20.2.9: {} - yargs-parser@21.1.1: {} - yargs-parser@22.0.0: {} yargs@17.0.1: @@ -6329,14 +5487,4 @@ snapshots: y18n: 5.0.8 yargs-parser: 20.2.9 - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - yocto-queue@0.1.0: {} diff --git a/src/pnpm-workspace.yaml b/src/pnpm-workspace.yaml index 65c74c4..2840480 100644 --- a/src/pnpm-workspace.yaml +++ b/src/pnpm-workspace.yaml @@ -4,7 +4,9 @@ packages: nodeLinker: hoisted onlyBuiltDependencies: - - better-sqlite3 - - esbuild - - sharp - bcrypt + - better-sqlite3 + - core-js + - esbuild + - protobufjs + - sharp diff --git a/src/user/package.json b/src/user/package.json index 6dfb965..ce84e40 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -29,9 +29,9 @@ "typebox": "^1.0.61" }, "devDependencies": { - "@types/node": "^22.19.1", + "@types/node": "^22.19.2", "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4" } } From 3140da7bab2b211e2e19b1a3ebd6e34fdb468bc3 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Mon, 1 Dec 2025 19:26:22 +0100 Subject: [PATCH 51/66] wip --- .../api/generated/.openapi-generator/FILES | 5 + .../src/api/generated/apis/OpenapiOtherApi.ts | 26 ++- .../generated/models/ChatTest200Response.ts | 20 +-- .../models/ChatTest200ResponsePayload.ts | 84 +++++++++ .../generated/models/DisableOtp400Response.ts | 93 ++++++++++ .../generated/models/EnableOtp400Response.ts | 93 ++++++++++ .../models/GetUser200ResponsePayload.ts | 16 ++ .../GetUser200ResponsePayloadSelfInfo.ts | 81 +++++++++ .../generated/models/StatusOtp200Response.ts | 22 +-- .../models/StatusOtp200ResponseAnyOf.ts | 20 +-- .../StatusOtp200ResponseAnyOfPayload.ts | 66 ++++++++ .../generated/models/StatusOtp400Response.ts | 93 ++++++++++ frontend/src/api/generated/models/index.ts | 5 + frontend/src/auth/index.ts | 61 ++++--- frontend/src/pages/index.ts | 1 + frontend/src/pages/login/login.ts | 97 ++++++++++- frontend/src/pages/login/totp.html | 11 ++ frontend/src/pages/profile/profile.html | 66 ++++++++ frontend/src/pages/profile/profile.ts | 159 ++++++++++++++++++ frontend/src/routing/index.ts | 9 +- frontend/src/routing/special_routes.ts | 2 - frontend/vite.config.js | 4 + src/@shared/src/auth/index.ts | 3 +- src/auth/openapi.json | 58 ++++++- src/auth/src/routes/disableOtp.ts | 8 + src/auth/src/routes/enableOtp.ts | 8 + src/auth/src/routes/statusOtp.ts | 35 +++- src/openapi.json | 72 +++++++- src/user/openapi.json | 14 ++ src/user/src/routes/info.ts | 11 ++ 30 files changed, 1148 insertions(+), 95 deletions(-) create mode 100644 frontend/src/api/generated/models/ChatTest200ResponsePayload.ts create mode 100644 frontend/src/api/generated/models/DisableOtp400Response.ts create mode 100644 frontend/src/api/generated/models/EnableOtp400Response.ts create mode 100644 frontend/src/api/generated/models/GetUser200ResponsePayloadSelfInfo.ts create mode 100644 frontend/src/api/generated/models/StatusOtp200ResponseAnyOfPayload.ts create mode 100644 frontend/src/api/generated/models/StatusOtp400Response.ts create mode 100644 frontend/src/pages/login/totp.html create mode 100644 frontend/src/pages/profile/profile.html create mode 100644 frontend/src/pages/profile/profile.ts diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index af12e66..b05ee98 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -2,15 +2,19 @@ apis/OpenapiOtherApi.ts apis/index.ts index.ts models/ChatTest200Response.ts +models/ChatTest200ResponsePayload.ts models/DisableOtp200Response.ts +models/DisableOtp400Response.ts models/DisableOtp401Response.ts models/DisableOtp500Response.ts models/EnableOtp200Response.ts models/EnableOtp200ResponsePayload.ts +models/EnableOtp400Response.ts models/EnableOtp401Response.ts models/EnableOtp401ResponseAnyOf.ts models/GetUser200Response.ts models/GetUser200ResponsePayload.ts +models/GetUser200ResponsePayloadSelfInfo.ts models/GetUser403Response.ts models/GetUser404Response.ts models/GetUserUserParameter.ts @@ -41,6 +45,7 @@ models/Signin500Response.ts models/StatusOtp200Response.ts models/StatusOtp200ResponseAnyOf.ts models/StatusOtp200ResponseAnyOf1.ts +models/StatusOtp200ResponseAnyOfPayload.ts models/StatusOtp401Response.ts models/StatusOtp500Response.ts models/index.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index fda4f60..c37580a 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -17,9 +17,11 @@ import * as runtime from '../runtime'; import type { ChatTest200Response, DisableOtp200Response, + DisableOtp400Response, DisableOtp401Response, DisableOtp500Response, EnableOtp200Response, + EnableOtp400Response, EnableOtp401Response, GetUser200Response, GetUser403Response, @@ -51,12 +53,16 @@ import { ChatTest200ResponseToJSON, DisableOtp200ResponseFromJSON, DisableOtp200ResponseToJSON, + DisableOtp400ResponseFromJSON, + DisableOtp400ResponseToJSON, DisableOtp401ResponseFromJSON, DisableOtp401ResponseToJSON, DisableOtp500ResponseFromJSON, DisableOtp500ResponseToJSON, EnableOtp200ResponseFromJSON, EnableOtp200ResponseToJSON, + EnableOtp400ResponseFromJSON, + EnableOtp400ResponseToJSON, EnableOtp401ResponseFromJSON, EnableOtp401ResponseToJSON, GetUser200ResponseFromJSON, @@ -174,7 +180,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -197,6 +203,10 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // Object response for status 200 return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp200ResponseFromJSON(jsonValue)); } + if (response.status === 400) { + // Object response for status 400 + return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp400ResponseFromJSON(jsonValue)); + } if (response.status === 401) { // Object response for status 401 return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue)); @@ -208,19 +218,19 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes // Only throw if responses were defined but none matched the actual status code - throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 500`); + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401, 500`); } /** */ - async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.disableOtpRaw(initOverrides); return await response.value(); } /** */ - async enableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async enableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -243,6 +253,10 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // Object response for status 200 return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp200ResponseFromJSON(jsonValue)); } + if (response.status === 400) { + // Object response for status 400 + return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp400ResponseFromJSON(jsonValue)); + } if (response.status === 401) { // Object response for status 401 return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp401ResponseFromJSON(jsonValue)); @@ -250,12 +264,12 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes // Only throw if responses were defined but none matched the actual status code - throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401`); + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401`); } /** */ - async enableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async enableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.enableOtpRaw(initOverrides); return await response.value(); } diff --git a/frontend/src/api/generated/models/ChatTest200Response.ts b/frontend/src/api/generated/models/ChatTest200Response.ts index 6feb66e..9fa3dd7 100644 --- a/frontend/src/api/generated/models/ChatTest200Response.ts +++ b/frontend/src/api/generated/models/ChatTest200Response.ts @@ -13,13 +13,13 @@ */ import { mapValues } from '../runtime'; -import type { GetUser200ResponsePayload } from './GetUser200ResponsePayload'; +import type { ChatTest200ResponsePayload } from './ChatTest200ResponsePayload'; import { - GetUser200ResponsePayloadFromJSON, - GetUser200ResponsePayloadFromJSONTyped, - GetUser200ResponsePayloadToJSON, - GetUser200ResponsePayloadToJSONTyped, -} from './GetUser200ResponsePayload'; + ChatTest200ResponsePayloadFromJSON, + ChatTest200ResponsePayloadFromJSONTyped, + ChatTest200ResponsePayloadToJSON, + ChatTest200ResponsePayloadToJSONTyped, +} from './ChatTest200ResponsePayload'; /** * @@ -41,10 +41,10 @@ export interface ChatTest200Response { msg: ChatTest200ResponseMsgEnum; /** * - * @type {GetUser200ResponsePayload} + * @type {ChatTest200ResponsePayload} * @memberof ChatTest200Response */ - payload: GetUser200ResponsePayload; + payload: ChatTest200ResponsePayload; } @@ -87,7 +87,7 @@ export function ChatTest200ResponseFromJSONTyped(json: any, ignoreDiscriminator: 'kind': json['kind'], 'msg': json['msg'], - 'payload': GetUser200ResponsePayloadFromJSON(json['payload']), + 'payload': ChatTest200ResponsePayloadFromJSON(json['payload']), }; } @@ -104,7 +104,7 @@ export function ChatTest200ResponseToJSONTyped(value?: ChatTest200Response | nul 'kind': value['kind'], 'msg': value['msg'], - 'payload': GetUser200ResponsePayloadToJSON(value['payload']), + 'payload': ChatTest200ResponsePayloadToJSON(value['payload']), }; } diff --git a/frontend/src/api/generated/models/ChatTest200ResponsePayload.ts b/frontend/src/api/generated/models/ChatTest200ResponsePayload.ts new file mode 100644 index 0000000..2017a31 --- /dev/null +++ b/frontend/src/api/generated/models/ChatTest200ResponsePayload.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChatTest200ResponsePayload + */ +export interface ChatTest200ResponsePayload { + /** + * + * @type {string} + * @memberof ChatTest200ResponsePayload + */ + name: string; + /** + * + * @type {string} + * @memberof ChatTest200ResponsePayload + */ + id: string; + /** + * + * @type {boolean} + * @memberof ChatTest200ResponsePayload + */ + guest: boolean; +} + +/** + * Check if a given object implements the ChatTest200ResponsePayload interface. + */ +export function instanceOfChatTest200ResponsePayload(value: object): value is ChatTest200ResponsePayload { + if (!('name' in value) || value['name'] === undefined) return false; + if (!('id' in value) || value['id'] === undefined) return false; + if (!('guest' in value) || value['guest'] === undefined) return false; + return true; +} + +export function ChatTest200ResponsePayloadFromJSON(json: any): ChatTest200ResponsePayload { + return ChatTest200ResponsePayloadFromJSONTyped(json, false); +} + +export function ChatTest200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChatTest200ResponsePayload { + if (json == null) { + return json; + } + return { + + 'name': json['name'], + 'id': json['id'], + 'guest': json['guest'], + }; +} + +export function ChatTest200ResponsePayloadToJSON(json: any): ChatTest200ResponsePayload { + return ChatTest200ResponsePayloadToJSONTyped(json, false); +} + +export function ChatTest200ResponsePayloadToJSONTyped(value?: ChatTest200ResponsePayload | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'name': value['name'], + 'id': value['id'], + 'guest': value['guest'], + }; +} + diff --git a/frontend/src/api/generated/models/DisableOtp400Response.ts b/frontend/src/api/generated/models/DisableOtp400Response.ts new file mode 100644 index 0000000..481e2be --- /dev/null +++ b/frontend/src/api/generated/models/DisableOtp400Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface DisableOtp400Response + */ +export interface DisableOtp400Response { + /** + * + * @type {string} + * @memberof DisableOtp400Response + */ + kind: DisableOtp400ResponseKindEnum; + /** + * + * @type {string} + * @memberof DisableOtp400Response + */ + msg: DisableOtp400ResponseMsgEnum; +} + + +/** + * @export + */ +export const DisableOtp400ResponseKindEnum = { + Failure: 'failure' +} as const; +export type DisableOtp400ResponseKindEnum = typeof DisableOtp400ResponseKindEnum[keyof typeof DisableOtp400ResponseKindEnum]; + +/** + * @export + */ +export const DisableOtp400ResponseMsgEnum = { + DisableOtpFailureGuest: 'disableOtp.failure.guest' +} as const; +export type DisableOtp400ResponseMsgEnum = typeof DisableOtp400ResponseMsgEnum[keyof typeof DisableOtp400ResponseMsgEnum]; + + +/** + * Check if a given object implements the DisableOtp400Response interface. + */ +export function instanceOfDisableOtp400Response(value: object): value is DisableOtp400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function DisableOtp400ResponseFromJSON(json: any): DisableOtp400Response { + return DisableOtp400ResponseFromJSONTyped(json, false); +} + +export function DisableOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): DisableOtp400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function DisableOtp400ResponseToJSON(json: any): DisableOtp400Response { + return DisableOtp400ResponseToJSONTyped(json, false); +} + +export function DisableOtp400ResponseToJSONTyped(value?: DisableOtp400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/EnableOtp400Response.ts b/frontend/src/api/generated/models/EnableOtp400Response.ts new file mode 100644 index 0000000..ec0b8e7 --- /dev/null +++ b/frontend/src/api/generated/models/EnableOtp400Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface EnableOtp400Response + */ +export interface EnableOtp400Response { + /** + * + * @type {string} + * @memberof EnableOtp400Response + */ + kind: EnableOtp400ResponseKindEnum; + /** + * + * @type {string} + * @memberof EnableOtp400Response + */ + msg: EnableOtp400ResponseMsgEnum; +} + + +/** + * @export + */ +export const EnableOtp400ResponseKindEnum = { + Failure: 'failure' +} as const; +export type EnableOtp400ResponseKindEnum = typeof EnableOtp400ResponseKindEnum[keyof typeof EnableOtp400ResponseKindEnum]; + +/** + * @export + */ +export const EnableOtp400ResponseMsgEnum = { + EnableOtpFailureGuest: 'enableOtp.failure.guest' +} as const; +export type EnableOtp400ResponseMsgEnum = typeof EnableOtp400ResponseMsgEnum[keyof typeof EnableOtp400ResponseMsgEnum]; + + +/** + * Check if a given object implements the EnableOtp400Response interface. + */ +export function instanceOfEnableOtp400Response(value: object): value is EnableOtp400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function EnableOtp400ResponseFromJSON(json: any): EnableOtp400Response { + return EnableOtp400ResponseFromJSONTyped(json, false); +} + +export function EnableOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): EnableOtp400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function EnableOtp400ResponseToJSON(json: any): EnableOtp400Response { + return EnableOtp400ResponseToJSONTyped(json, false); +} + +export function EnableOtp400ResponseToJSONTyped(value?: EnableOtp400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/GetUser200ResponsePayload.ts b/frontend/src/api/generated/models/GetUser200ResponsePayload.ts index f41f951..79b420a 100644 --- a/frontend/src/api/generated/models/GetUser200ResponsePayload.ts +++ b/frontend/src/api/generated/models/GetUser200ResponsePayload.ts @@ -13,6 +13,14 @@ */ import { mapValues } from '../runtime'; +import type { GetUser200ResponsePayloadSelfInfo } from './GetUser200ResponsePayloadSelfInfo'; +import { + GetUser200ResponsePayloadSelfInfoFromJSON, + GetUser200ResponsePayloadSelfInfoFromJSONTyped, + GetUser200ResponsePayloadSelfInfoToJSON, + GetUser200ResponsePayloadSelfInfoToJSONTyped, +} from './GetUser200ResponsePayloadSelfInfo'; + /** * * @export @@ -37,6 +45,12 @@ export interface GetUser200ResponsePayload { * @memberof GetUser200ResponsePayload */ guest: boolean; + /** + * + * @type {GetUser200ResponsePayloadSelfInfo} + * @memberof GetUser200ResponsePayload + */ + selfInfo?: GetUser200ResponsePayloadSelfInfo; } /** @@ -62,6 +76,7 @@ export function GetUser200ResponsePayloadFromJSONTyped(json: any, ignoreDiscrimi 'name': json['name'], 'id': json['id'], 'guest': json['guest'], + 'selfInfo': json['selfInfo'] == null ? undefined : GetUser200ResponsePayloadSelfInfoFromJSON(json['selfInfo']), }; } @@ -79,6 +94,7 @@ export function GetUser200ResponsePayloadToJSONTyped(value?: GetUser200ResponseP 'name': value['name'], 'id': value['id'], 'guest': value['guest'], + 'selfInfo': GetUser200ResponsePayloadSelfInfoToJSON(value['selfInfo']), }; } diff --git a/frontend/src/api/generated/models/GetUser200ResponsePayloadSelfInfo.ts b/frontend/src/api/generated/models/GetUser200ResponsePayloadSelfInfo.ts new file mode 100644 index 0000000..0a37bac --- /dev/null +++ b/frontend/src/api/generated/models/GetUser200ResponsePayloadSelfInfo.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface GetUser200ResponsePayloadSelfInfo + */ +export interface GetUser200ResponsePayloadSelfInfo { + /** + * + * @type {string} + * @memberof GetUser200ResponsePayloadSelfInfo + */ + loginName?: string; + /** + * + * @type {string} + * @memberof GetUser200ResponsePayloadSelfInfo + */ + providerId?: string; + /** + * + * @type {string} + * @memberof GetUser200ResponsePayloadSelfInfo + */ + providerUser?: string; +} + +/** + * Check if a given object implements the GetUser200ResponsePayloadSelfInfo interface. + */ +export function instanceOfGetUser200ResponsePayloadSelfInfo(value: object): value is GetUser200ResponsePayloadSelfInfo { + return true; +} + +export function GetUser200ResponsePayloadSelfInfoFromJSON(json: any): GetUser200ResponsePayloadSelfInfo { + return GetUser200ResponsePayloadSelfInfoFromJSONTyped(json, false); +} + +export function GetUser200ResponsePayloadSelfInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): GetUser200ResponsePayloadSelfInfo { + if (json == null) { + return json; + } + return { + + 'loginName': json['login_name'] == null ? undefined : json['login_name'], + 'providerId': json['provider_id'] == null ? undefined : json['provider_id'], + 'providerUser': json['provider_user'] == null ? undefined : json['provider_user'], + }; +} + +export function GetUser200ResponsePayloadSelfInfoToJSON(json: any): GetUser200ResponsePayloadSelfInfo { + return GetUser200ResponsePayloadSelfInfoToJSONTyped(json, false); +} + +export function GetUser200ResponsePayloadSelfInfoToJSONTyped(value?: GetUser200ResponsePayloadSelfInfo | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'login_name': value['loginName'], + 'provider_id': value['providerId'], + 'provider_user': value['providerUser'], + }; +} + diff --git a/frontend/src/api/generated/models/StatusOtp200Response.ts b/frontend/src/api/generated/models/StatusOtp200Response.ts index 7dec45c..522edc1 100644 --- a/frontend/src/api/generated/models/StatusOtp200Response.ts +++ b/frontend/src/api/generated/models/StatusOtp200Response.ts @@ -13,13 +13,6 @@ */ import { mapValues } from '../runtime'; -import type { EnableOtp200ResponsePayload } from './EnableOtp200ResponsePayload'; -import { - EnableOtp200ResponsePayloadFromJSON, - EnableOtp200ResponsePayloadFromJSONTyped, - EnableOtp200ResponsePayloadToJSON, - EnableOtp200ResponsePayloadToJSONTyped, -} from './EnableOtp200ResponsePayload'; import type { StatusOtp200ResponseAnyOf } from './StatusOtp200ResponseAnyOf'; import { StatusOtp200ResponseAnyOfFromJSON, @@ -27,6 +20,13 @@ import { StatusOtp200ResponseAnyOfToJSON, StatusOtp200ResponseAnyOfToJSONTyped, } from './StatusOtp200ResponseAnyOf'; +import type { StatusOtp200ResponseAnyOfPayload } from './StatusOtp200ResponseAnyOfPayload'; +import { + StatusOtp200ResponseAnyOfPayloadFromJSON, + StatusOtp200ResponseAnyOfPayloadFromJSONTyped, + StatusOtp200ResponseAnyOfPayloadToJSON, + StatusOtp200ResponseAnyOfPayloadToJSONTyped, +} from './StatusOtp200ResponseAnyOfPayload'; import type { StatusOtp200ResponseAnyOf1 } from './StatusOtp200ResponseAnyOf1'; import { StatusOtp200ResponseAnyOf1FromJSON, @@ -55,10 +55,10 @@ export interface StatusOtp200Response { msg: StatusOtp200ResponseMsgEnum; /** * - * @type {EnableOtp200ResponsePayload} + * @type {StatusOtp200ResponseAnyOfPayload} * @memberof StatusOtp200Response */ - payload: EnableOtp200ResponsePayload; + payload: StatusOtp200ResponseAnyOfPayload; } @@ -101,7 +101,7 @@ export function StatusOtp200ResponseFromJSONTyped(json: any, ignoreDiscriminator 'kind': json['kind'], 'msg': json['msg'], - 'payload': EnableOtp200ResponsePayloadFromJSON(json['payload']), + 'payload': StatusOtp200ResponseAnyOfPayloadFromJSON(json['payload']), }; } @@ -118,7 +118,7 @@ export function StatusOtp200ResponseToJSONTyped(value?: StatusOtp200Response | n 'kind': value['kind'], 'msg': value['msg'], - 'payload': EnableOtp200ResponsePayloadToJSON(value['payload']), + 'payload': StatusOtp200ResponseAnyOfPayloadToJSON(value['payload']), }; } diff --git a/frontend/src/api/generated/models/StatusOtp200ResponseAnyOf.ts b/frontend/src/api/generated/models/StatusOtp200ResponseAnyOf.ts index 5750e63..0ea3524 100644 --- a/frontend/src/api/generated/models/StatusOtp200ResponseAnyOf.ts +++ b/frontend/src/api/generated/models/StatusOtp200ResponseAnyOf.ts @@ -13,13 +13,13 @@ */ import { mapValues } from '../runtime'; -import type { EnableOtp200ResponsePayload } from './EnableOtp200ResponsePayload'; +import type { StatusOtp200ResponseAnyOfPayload } from './StatusOtp200ResponseAnyOfPayload'; import { - EnableOtp200ResponsePayloadFromJSON, - EnableOtp200ResponsePayloadFromJSONTyped, - EnableOtp200ResponsePayloadToJSON, - EnableOtp200ResponsePayloadToJSONTyped, -} from './EnableOtp200ResponsePayload'; + StatusOtp200ResponseAnyOfPayloadFromJSON, + StatusOtp200ResponseAnyOfPayloadFromJSONTyped, + StatusOtp200ResponseAnyOfPayloadToJSON, + StatusOtp200ResponseAnyOfPayloadToJSONTyped, +} from './StatusOtp200ResponseAnyOfPayload'; /** * @@ -41,10 +41,10 @@ export interface StatusOtp200ResponseAnyOf { msg: StatusOtp200ResponseAnyOfMsgEnum; /** * - * @type {EnableOtp200ResponsePayload} + * @type {StatusOtp200ResponseAnyOfPayload} * @memberof StatusOtp200ResponseAnyOf */ - payload: EnableOtp200ResponsePayload; + payload: StatusOtp200ResponseAnyOfPayload; } @@ -87,7 +87,7 @@ export function StatusOtp200ResponseAnyOfFromJSONTyped(json: any, ignoreDiscrimi 'kind': json['kind'], 'msg': json['msg'], - 'payload': EnableOtp200ResponsePayloadFromJSON(json['payload']), + 'payload': StatusOtp200ResponseAnyOfPayloadFromJSON(json['payload']), }; } @@ -104,7 +104,7 @@ export function StatusOtp200ResponseAnyOfToJSONTyped(value?: StatusOtp200Respons 'kind': value['kind'], 'msg': value['msg'], - 'payload': EnableOtp200ResponsePayloadToJSON(value['payload']), + 'payload': StatusOtp200ResponseAnyOfPayloadToJSON(value['payload']), }; } diff --git a/frontend/src/api/generated/models/StatusOtp200ResponseAnyOfPayload.ts b/frontend/src/api/generated/models/StatusOtp200ResponseAnyOfPayload.ts new file mode 100644 index 0000000..260af1d --- /dev/null +++ b/frontend/src/api/generated/models/StatusOtp200ResponseAnyOfPayload.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface StatusOtp200ResponseAnyOfPayload + */ +export interface StatusOtp200ResponseAnyOfPayload { + /** + * The otp secret + * @type {string} + * @memberof StatusOtp200ResponseAnyOfPayload + */ + secret: string; +} + +/** + * Check if a given object implements the StatusOtp200ResponseAnyOfPayload interface. + */ +export function instanceOfStatusOtp200ResponseAnyOfPayload(value: object): value is StatusOtp200ResponseAnyOfPayload { + if (!('secret' in value) || value['secret'] === undefined) return false; + return true; +} + +export function StatusOtp200ResponseAnyOfPayloadFromJSON(json: any): StatusOtp200ResponseAnyOfPayload { + return StatusOtp200ResponseAnyOfPayloadFromJSONTyped(json, false); +} + +export function StatusOtp200ResponseAnyOfPayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): StatusOtp200ResponseAnyOfPayload { + if (json == null) { + return json; + } + return { + + 'secret': json['secret'], + }; +} + +export function StatusOtp200ResponseAnyOfPayloadToJSON(json: any): StatusOtp200ResponseAnyOfPayload { + return StatusOtp200ResponseAnyOfPayloadToJSONTyped(json, false); +} + +export function StatusOtp200ResponseAnyOfPayloadToJSONTyped(value?: StatusOtp200ResponseAnyOfPayload | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'secret': value['secret'], + }; +} + diff --git a/frontend/src/api/generated/models/StatusOtp400Response.ts b/frontend/src/api/generated/models/StatusOtp400Response.ts new file mode 100644 index 0000000..a952478 --- /dev/null +++ b/frontend/src/api/generated/models/StatusOtp400Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface StatusOtp400Response + */ +export interface StatusOtp400Response { + /** + * + * @type {string} + * @memberof StatusOtp400Response + */ + kind: StatusOtp400ResponseKindEnum; + /** + * + * @type {string} + * @memberof StatusOtp400Response + */ + msg: StatusOtp400ResponseMsgEnum; +} + + +/** + * @export + */ +export const StatusOtp400ResponseKindEnum = { + Failure: 'failure' +} as const; +export type StatusOtp400ResponseKindEnum = typeof StatusOtp400ResponseKindEnum[keyof typeof StatusOtp400ResponseKindEnum]; + +/** + * @export + */ +export const StatusOtp400ResponseMsgEnum = { + StatusOtpFailureGuest: 'statusOtp.failure.guest' +} as const; +export type StatusOtp400ResponseMsgEnum = typeof StatusOtp400ResponseMsgEnum[keyof typeof StatusOtp400ResponseMsgEnum]; + + +/** + * Check if a given object implements the StatusOtp400Response interface. + */ +export function instanceOfStatusOtp400Response(value: object): value is StatusOtp400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function StatusOtp400ResponseFromJSON(json: any): StatusOtp400Response { + return StatusOtp400ResponseFromJSONTyped(json, false); +} + +export function StatusOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): StatusOtp400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function StatusOtp400ResponseToJSON(json: any): StatusOtp400Response { + return StatusOtp400ResponseToJSONTyped(json, false); +} + +export function StatusOtp400ResponseToJSONTyped(value?: StatusOtp400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index a4059b0..a3cdb3a 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -1,15 +1,19 @@ /* tslint:disable */ /* eslint-disable */ export * from './ChatTest200Response'; +export * from './ChatTest200ResponsePayload'; export * from './DisableOtp200Response'; +export * from './DisableOtp400Response'; export * from './DisableOtp401Response'; export * from './DisableOtp500Response'; export * from './EnableOtp200Response'; export * from './EnableOtp200ResponsePayload'; +export * from './EnableOtp400Response'; export * from './EnableOtp401Response'; export * from './EnableOtp401ResponseAnyOf'; export * from './GetUser200Response'; export * from './GetUser200ResponsePayload'; +export * from './GetUser200ResponsePayloadSelfInfo'; export * from './GetUser403Response'; export * from './GetUser404Response'; export * from './GetUserUserParameter'; @@ -40,5 +44,6 @@ export * from './Signin500Response'; export * from './StatusOtp200Response'; export * from './StatusOtp200ResponseAnyOf'; export * from './StatusOtp200ResponseAnyOf1'; +export * from './StatusOtp200ResponseAnyOfPayload'; export * from './StatusOtp401Response'; export * from './StatusOtp500Response'; diff --git a/frontend/src/auth/index.ts b/frontend/src/auth/index.ts index 57159b7..7807bf9 100644 --- a/frontend/src/auth/index.ts +++ b/frontend/src/auth/index.ts @@ -2,49 +2,54 @@ import { showError } from "@app/toast"; import client from '@app/api'; export type User = { - id: string; - guest: boolean; - name: string; + id: string; + guest: boolean; + name: string; + selfInfo?: { + loginName?: string; + provider_id?: string; + provider_user?: string; + } }; let currentUser: User | null = null; export function getUser(): Readonly | null { - return currentUser; + return currentUser; } export function isLogged(): boolean { - return currentUser !== null; + return currentUser !== null; } export function setUser(newUser: User | null) { - currentUser = newUser; + currentUser = newUser; } export async function updateUser(): Promise | null> { - try { - let res = await client.getUser({ user: 'me' }); + try { + let res = await client.getUser({ user: 'me' }); - if (res.kind === "success") { - setUser(res.payload); - return res.payload; - } else if (res.kind === "failure") { - // well no user :D - setUser(null); - return null; - } else if (res.kind === "notLoggedIn") { - setUser(null); - return null; - } else { - setUser(null); - showError(`unknown response: ${JSON.stringify(res)}`); - return null; - } - } catch (e) { - setUser(null); - showError(`failed to get user: ${e}`); - return null; - } + if (res.kind === "success") { + setUser(res.payload); + return res.payload; + } else if (res.kind === "failure") { + // well no user :D + setUser(null); + return null; + } else if (res.kind === "notLoggedIn") { + setUser(null); + return null; + } else { + setUser(null); + showError(`unknown response: ${JSON.stringify(res)}`); + return null; + } + } catch (e) { + setUser(null); + showError(`failed to get user: ${e}`); + return null; + } } Object.assign(window as any, { getUser, setUser, updateUser, isLogged }); diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index 5ad7a75..dfab156 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -4,6 +4,7 @@ import './chat/chat.ts' import './login/login.ts' import './signin/signin.ts' import './ttt/ttt.ts' +import './profile/profile.ts' // ---- Initial load ---- setTitle(""); diff --git a/frontend/src/pages/login/login.ts b/frontend/src/pages/login/login.ts index c98a84c..2cd0a14 100644 --- a/frontend/src/pages/login/login.ts +++ b/frontend/src/pages/login/login.ts @@ -5,14 +5,98 @@ import { type RouteHandlerParams, type RouteHandlerReturn, } from "@app/routing"; -import { showError, showInfo, showSuccess } from "@app/toast"; +import Cookie from "js-cookie"; import authHtml from "./login.html?raw"; import client from "@app/api"; -import { updateUser } from "@app/auth"; -import Cookie from "js-cookie"; -import loggedInHtml from "./alreadyLoggedin.html?raw"; import cuteCat from "./cuteCat.png"; +import loggedInHtml from "./alreadyLoggedin.html?raw"; +import totpHtml from "./totp.html?raw"; import { isNullish } from "@app/utils"; +import { showError, showInfo, showSuccess } from "@app/toast"; +import { updateUser } from "@app/auth"; + +const TOTP_LENGTH = 6; + +async function handleOtp(app: HTMLElement, token: string, returnTo: string | null) { + app.innerHTML = totpHtml; + + const container = app.querySelector("#totp-container")!; + container.innerHTML = ""; + + const inputs: HTMLInputElement[] = []; + + for (let i = 0; i < TOTP_LENGTH; i++) { + const input = document.createElement("input"); + input.maxLength = 1; + input.inputMode = "numeric"; + input.className = + "w-12 h-12 text-center text-xl border border-gray-300 rounded " + + "focus:outline-none focus:ring-2 focus:ring-blue-500"; + + container.appendChild(input); + inputs.push(input); + + // Handle typing a digit + input.addEventListener("input", async () => { + const value = input.value.replace(/\D/g, ""); + input.value = value; + + // Auto-advance when filled + if (value && i < TOTP_LENGTH - 1) { + inputs[i + 1].focus(); + } + await checkComplete(); + }); + + // Handle backspace + input.addEventListener("keydown", (e) => { + if (e.key === "Backspace" && !input.value && i > 0) { + inputs[i - 1].focus(); + } + }); + + // Handle pasting a full code + input.addEventListener("paste", (e: ClipboardEvent) => { + const pasted = e.clipboardData?.getData("text") ?? ""; + const digits = pasted.replace(/\D/g, "").slice(0, TOTP_LENGTH); + + if (digits.length > 1) { + e.preventDefault(); + digits.split("").forEach((d, idx) => { + if (inputs[idx]) inputs[idx].value = d; + }); + if (digits.length === TOTP_LENGTH) checkComplete(); + } + }); + } + + // Check if all digits are entered and then call totpSend + async function checkComplete() { + const code = inputs.map((i) => i.value).join(""); + if (code.length === TOTP_LENGTH && /^[0-9]+$/.test(code)) { + let res = await client.loginOtp({ + loginOtpRequest: { + code, token, + } + }) + + if (res.kind === "success") { + Cookie.set("token", res.payload.token, { + path: "/", + sameSite: "lax", + }); + if (returnTo !== null) navigateTo(returnTo); + else navigateTo("/"); + } + else if (res.kind === "failed") { + showError(`Failed to authenticate: ${res.msg}`); + } + } + } + + inputs[0].focus(); +} + async function handleLogin( _url: string, @@ -67,7 +151,7 @@ async function handleLogin( return showError( "Error while rendering the page: no form found", ); - fLogin.addEventListener("submit", async function (e: SubmitEvent) { + fLogin.addEventListener("submit", async function(e: SubmitEvent) { e.preventDefault(); let form = e.target as HTMLFormElement | null; if (form === null) return showError("Failed to send form..."); @@ -109,8 +193,7 @@ async function handleLogin( break; } case "otpRequired": { - showInfo("Got ask OTP, not yet implemented"); - break; + return await handleOtp(app!, res.payload.token, returnTo); } case "failed": { showError(`Failed to login: ${res.msg}`); diff --git a/frontend/src/pages/login/totp.html b/frontend/src/pages/login/totp.html new file mode 100644 index 0000000..48d3703 --- /dev/null +++ b/frontend/src/pages/login/totp.html @@ -0,0 +1,11 @@ +
+
+

+ Welcome to ft boules +

+
+ +
+
+
+
diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html new file mode 100644 index 0000000..1c56014 --- /dev/null +++ b/frontend/src/pages/profile/profile.html @@ -0,0 +1,66 @@ +
+
+

Edit Profile

+ + + + +
+ +
+
+ + +
+ + + +
+ + + +
+ + + +
+ + +
+

Two-Factor Authentication (TOTP)

+ +
+ Status: Disabled + +
+ + + + + +
+
+ + +
+ +
+
diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts new file mode 100644 index 0000000..b3d6e77 --- /dev/null +++ b/frontend/src/pages/profile/profile.ts @@ -0,0 +1,159 @@ +import { addRoute, navigateTo, setTitle } from "@app/routing"; +import { showError } from "@app/toast"; +import page from './profile.html?raw' +import { updateUser } from "@app/auth"; +import { isNullish } from "@app/utils"; +import client from "@app/api"; + + +async function route(url: string, _args: { [k: string]: string }) { + setTitle('Edit Profile') + return { + html: page, postInsert: async (app: HTMLElement | undefined) => { + const user = await updateUser(); + if (isNullish(user)) + return showError('No User'); + if (isNullish(app)) + return showError('Failed to render'); + let totpState = await (async () => { + let res = await client.statusOtp(); + if (res.kind === "success") + return { + enabled: (res.msg as string) === "statusOtp.success.enabled", + secret: ((res.msg as string) === "statusOtp.success.enabled") ? res.payload.secret : null, + }; + else { + showError('Failed to get OTP status') + return { + enabled: false, secret: null, + } + } + + })() + // ---- Simulated State ---- + let totpEnabled = totpState.enabled; + let totpSecret = totpState.secret; // would come from backend + + let guestBox = app.querySelector("#isGuestBox")!; + let displayNameWrapper = app.querySelector("#displayNameWrapper")!; + let displayNameBox = app.querySelector("#displayNameBox")!; + let displayNameButton = app.querySelector("#displayNameButton")!; + let loginNameWrapper = app.querySelector("#loginNameWrapper")!; + let loginNameBox = app.querySelector("#loginNameBox")!; + let passwordWrapper = app.querySelector("#passwordWrapper")!; + let passwordBox = app.querySelector("#passwordBox")!; + let passwordButton = app.querySelector("#passwordButton")!; + + + if (!isNullish(user.selfInfo?.loginName)) + loginNameBox.innerText = user.selfInfo?.loginName; + else + loginNameBox.innerHTML = 'You don\'t have a login name'; + displayNameBox.value = user.name; + + guestBox.hidden = !user.guest; + + // ---- DOM Elements ---- + const totpStatusText = app.querySelector("#totpStatusText")!; + const enableBtn = app.querySelector("#enableTotp")!; + const disableBtn = app.querySelector("#disableTotp")!; + const showSecretBtn = app.querySelector("#showSecret")!; + const secretBox = app.querySelector("#totpSecretBox")!; + + if (user.guest) { + for (let c of passwordButton.classList.values()) { + if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + passwordButton.classList.remove(c); + } + passwordButton.disabled = true; + passwordButton.classList.add('bg-gray-700', 'hover:bg-gray-700'); + + passwordBox.disabled = true; + passwordBox.classList.add('color-white'); + + for (let c of displayNameButton.classList.values()) { + if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + displayNameButton.classList.remove(c); + } + displayNameButton.disabled = true; + displayNameButton.classList.add('bg-gray-700'); + displayNameButton.classList.add('color-white'); + + displayNameBox.disabled = true; + displayNameBox.classList.add('color-white'); + + for (let c of enableBtn.classList.values()) { + if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + enableBtn.classList.remove(c); + } + for (let c of disableBtn.classList.values()) { + if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + disableBtn.classList.remove(c); + } + for (let c of showSecretBtn.classList.values()) { + if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + showSecretBtn.classList.remove(c); + } + enableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); + disableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); + showSecretBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); + + enableBtn.disabled = true; + disableBtn.disabled = true; + showSecretBtn.disabled = true; + } + + + // ---- Update UI ---- + function refreshTotpUI() { + if (totpEnabled) { + totpStatusText.textContent = "Status: Enabled"; + + enableBtn.classList.add("hidden"); + disableBtn.classList.remove("hidden"); + showSecretBtn.classList.remove("hidden"); + } else { + totpStatusText.textContent = "Status: Disabled"; + + enableBtn.classList.remove("hidden"); + disableBtn.classList.add("hidden"); + showSecretBtn.classList.add("hidden"); + secretBox.classList.add("hidden"); + } + } + + // ---- Button Events ---- + enableBtn.onclick = async () => { + let res = await client.enableOtp(); + if (res.kind === "success") { + navigateTo(url); + } + else { + showError(`failed to activate OTP: ${res.msg}`); + } + }; + + disableBtn.onclick = async () => { + let res = await client.disableOtp(); + if (res.kind === "success") { + navigateTo(url); + } + else { + showError(`failed to deactivate OTP: ${res.msg}`); + } + }; + + showSecretBtn.onclick = () => { + secretBox.textContent = `TOTP Secret: ${totpSecret}`; + secretBox.classList.toggle("hidden"); + }; + + // Initialize UI state + refreshTotpUI(); + } + }; +} + + + +addRoute('/profile', route) diff --git a/frontend/src/routing/index.ts b/frontend/src/routing/index.ts index 0c0b9a4..02cd433 100644 --- a/frontend/src/routing/index.ts +++ b/frontend/src/routing/index.ts @@ -46,12 +46,11 @@ export class RouteHandlerData { constructor(url: string, handler: RouteHandler, special_args: Partial) { this.special_args = Object.assign({}, RouteHandlerData.SPECIAL_ARGS_DEFAULT); Object.assign(this.special_args, special_args); - console.log(url, this.special_args); let parsed = RouteHandlerData.parseUrl(url); this.handler = handler; - this.parts = parsed.parts; - this.url = parsed.parts.map((v, i) => v ?? `:${i}`).reduce((p, c) => `${p}/${c}`, ''); + this.parts = parsed.parts.filter(p => p?.length !== 0); + this.url = parsed.parts.filter(p => p?.length !== 0).map((v, i) => v ?? `:${i}`).reduce((p, c) => `${p}/${c}`, ''); this.args = parsed.args; this.orignal_url = parsed.original; } @@ -99,7 +98,7 @@ function urlToParts(url: string): string[] { let parts = trimed.split('/'); if (parts.at(0) === 'app') parts.shift(); - return parts; + return parts.filter(p => p.length !== 0); } function setupRoutes(): [ @@ -190,8 +189,6 @@ export async function handleRoute() { } let user = await updateUser(); - console.log(route_handler); - console.log(user, !route_handler.special_args.bypass_auth, user === null && !route_handler.special_args.bypass_auth); if (user === null && !route_handler.special_args.bypass_auth) return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`) const app = document.getElementById('app')!; diff --git a/frontend/src/routing/special_routes.ts b/frontend/src/routing/special_routes.ts index b0c5ee8..5b7b7f6 100644 --- a/frontend/src/routing/special_routes.ts +++ b/frontend/src/routing/special_routes.ts @@ -2,8 +2,6 @@ import { escapeHTML } from "@app/utils"; import { getRoute, type RouteHandlerParams } from "@app/routing"; export async function route_404(url: string, _args: RouteHandlerParams): Promise { - console.log(`asked about route '${url}: not found'`) - console.log(getRoute()) return `
404 - Not Found

diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 7a6a7e3..00dab97 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,6 +7,10 @@ export default defineConfig({ tailwindcss(), tsconfigPaths(), ], + build: { + minify: false, + sourcemap: true, + }, server: { hmr: { protocol: 'ws', diff --git a/src/@shared/src/auth/index.ts b/src/@shared/src/auth/index.ts index 44a1312..dba527a 100644 --- a/src/@shared/src/auth/index.ts +++ b/src/@shared/src/auth/index.ts @@ -14,6 +14,7 @@ const kRouteAuthDone = Symbol('shared-route-auth-done'); type AuthedUser = { id: UserId; name: string; + guest: boolean; }; declare module 'fastify' { @@ -118,7 +119,7 @@ export const authPlugin = fp<{ onlySchema?: boolean }>(async (fastify, { onlySch .clearCookie('token', { path: '/' }) .makeResponse(401, 'notLoggedIn', 'auth.noUser'); } - req.authUser = { id: user.id, name: user.display_name }; + req.authUser = { id: user.id, name: user.name, guest: user.guest }; } catch { return res diff --git a/src/auth/openapi.json b/src/auth/openapi.json index 6522bdf..c34cda3 100644 --- a/src/auth/openapi.json +++ b/src/auth/openapi.json @@ -38,6 +38,32 @@ } } }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "disableOtp.failure.guest" + ] + } + } + } + } + } + }, "401": { "description": "Default Response", "content": { @@ -139,6 +165,32 @@ } } }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "enableOtp.failure.guest" + ] + } + } + } + } + } + }, "401": { "description": "Default Response", "content": { @@ -846,12 +898,12 @@ "payload": { "type": "object", "required": [ - "url" + "secret" ], "properties": { - "url": { + "secret": { "type": "string", - "description": "The otp url to feed into a 2fa app" + "description": "The otp secret" } } } diff --git a/src/auth/src/routes/disableOtp.ts b/src/auth/src/routes/disableOtp.ts index 2336362..4ef9292 100644 --- a/src/auth/src/routes/disableOtp.ts +++ b/src/auth/src/routes/disableOtp.ts @@ -7,6 +7,7 @@ import { typeResponse, isNullish } from '@shared/utils'; export const DisableOtpRes = { '200': typeResponse('success', 'disableOtp.success'), '500': typeResponse('failure', 'disableOtp.failure.generic'), + '400': typeResponse('failure', 'disableOtp.failure.guest'), }; @@ -18,6 +19,13 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { async function(req, res) { void res; if (isNullish(req.authUser)) { return res.makeResponse(500, 'failure', 'disableOtp.failure.generic'); } + if (req.authUser.guest) { + return res.makeResponse( + 400, + 'failure', + 'disableOtp.failure.guest', + ); + } this.db.deleteUserOtpSecret(req.authUser.id); return res.makeResponse(200, 'success', 'disableOtp.success'); }, diff --git a/src/auth/src/routes/enableOtp.ts b/src/auth/src/routes/enableOtp.ts index 0fe2d78..7732c2c 100644 --- a/src/auth/src/routes/enableOtp.ts +++ b/src/auth/src/routes/enableOtp.ts @@ -10,6 +10,7 @@ export const EnableOtpRes = { url: Type.String({ description: 'The otp url to feed into a 2fa app' }), }), '401': typeResponse('failure', ['enableOtp.failure.noUser', 'enableOtp.failure.noSecret']), + '400': typeResponse('failure', ['enableOtp.failure.guest']), }; export type EnableOtpRes = MakeStaticResponse; @@ -21,6 +22,13 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { { schema: { response: EnableOtpRes, operationId: 'enableOtp' }, config: { requireAuth: true } }, async function(req, res) { if (isNullish(req.authUser)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noUser'); } + if (req.authUser.guest) { + return res.makeResponse( + 400, + 'failure', + 'enableOtp.failure.guest', + ); + } const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); if (isNullish(otpSecret)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noSecret'); } diff --git a/src/auth/src/routes/statusOtp.ts b/src/auth/src/routes/statusOtp.ts index 3236619..41d15a7 100644 --- a/src/auth/src/routes/statusOtp.ts +++ b/src/auth/src/routes/statusOtp.ts @@ -2,12 +2,12 @@ import { FastifyPluginAsync } from 'fastify'; import { Type } from 'typebox'; import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils'; -import { Otp } from '@shared/auth'; - export const StatusOtpRes = { 200: Type.Union([ - typeResponse('success', 'statusOtp.success.enabled', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), + typeResponse('success', 'statusOtp.success.enabled', { + secret: Type.String({ description: 'The otp secret' }), + }), typeResponse('success', 'statusOtp.success.disabled'), ]), 500: typeResponse('failure', 'statusOtp.failure.generic'), @@ -19,13 +19,32 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { void _opts; fastify.get( '/api/auth/statusOtp', - { schema: { response: StatusOtpRes, operationId: 'statusOtp' }, config: { requireAuth: true } }, + { + schema: { response: StatusOtpRes, operationId: 'statusOtp' }, + config: { requireAuth: true }, + }, async function(req, res) { - if (isNullish(req.authUser)) { return res.makeResponse(500, 'failure', 'statusOtp.failure.generic'); } + if (isNullish(req.authUser)) { + return res.makeResponse( + 500, + 'failure', + 'statusOtp.failure.generic', + ); + } const otpSecret = this.db.getUserOtpSecret(req.authUser.id); - if (isNullish(otpSecret)) { return res.makeResponse(200, 'success', 'statusOtp.success.disabled'); } - const otp = new Otp({ secret: otpSecret }); - return res.makeResponse(200, 'success', 'statusOtp.success.enabled', { url: otp.totpURL }); + if (isNullish(otpSecret)) { + return res.makeResponse( + 200, + 'success', + 'statusOtp.success.disabled', + ); + } + return res.makeResponse( + 200, + 'success', + 'statusOtp.success.enabled', + { secret: otpSecret }, + ); }, ); }; diff --git a/src/openapi.json b/src/openapi.json index 4327973..4ab32ff 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -51,6 +51,32 @@ } } }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "disableOtp.failure.guest" + ] + } + } + } + } + } + }, "401": { "description": "Default Response", "content": { @@ -155,6 +181,32 @@ } } }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "enableOtp.failure.guest" + ] + } + } + } + } + } + }, "401": { "description": "Default Response", "content": { @@ -883,12 +935,12 @@ "payload": { "type": "object", "required": [ - "url" + "secret" ], "properties": { - "url": { + "secret": { "type": "string", - "description": "The otp url to feed into a 2fa app" + "description": "The otp secret" } } } @@ -1069,6 +1121,20 @@ }, "guest": { "type": "boolean" + }, + "selfInfo": { + "type": "object", + "properties": { + "login_name": { + "type": "string" + }, + "provider_id": { + "type": "string" + }, + "provider_user": { + "type": "string" + } + } } } } diff --git a/src/user/openapi.json b/src/user/openapi.json index c785e10..87744dc 100644 --- a/src/user/openapi.json +++ b/src/user/openapi.json @@ -72,6 +72,20 @@ }, "guest": { "type": "boolean" + }, + "selfInfo": { + "type": "object", + "properties": { + "login_name": { + "type": "string" + }, + "provider_id": { + "type": "string" + }, + "provider_user": { + "type": "string" + } + } } } } diff --git a/src/user/src/routes/info.ts b/src/user/src/routes/info.ts index edf7c00..869835e 100644 --- a/src/user/src/routes/info.ts +++ b/src/user/src/routes/info.ts @@ -7,6 +7,11 @@ import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils'; export const UserInfoRes = { '200': typeResponse('success', 'userinfo.success', { name: Type.String(), id: Type.String(), guest: Type.Boolean(), + selfInfo: Type.Optional(Type.Object({ + login_name: Type.Optional(Type.String()), + provider_id: Type.Optional(Type.String()), + provider_user: Type.Optional(Type.String()), + })), }), '403': typeResponse('failure', 'userinfo.failure.notLoggedIn'), '404': typeResponse('failure', 'userinfo.failure.unknownUser'), @@ -38,6 +43,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { if (req.params.user === 'me') { req.params.user = req.authUser.id; } + const askSelf = req.params.user === req.authUser.id; const user = this.db.getUser(req.params.user); if (isNullish(user)) { @@ -57,6 +63,11 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { // ``` // is the same as `val = !!something` guest: !!user.guest, + selfInfo: askSelf ? { + login_name: user.login, + provider_id: user.provider_name, + provider_user: user.provider_unique, + } : null, }; return res.makeResponse(200, 'success', 'userinfo.success', payload); From bb7037d51536e8f51d207f90d518078c01ad5fc5 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Thu, 4 Dec 2025 16:03:52 +0100 Subject: [PATCH 52/66] feat(nginx): Fixed going to `/` not working --- frontend/index.html | 4 +++- nginx/conf/locations/app.conf | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 47a018d..5163217 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -25,9 +25,11 @@ class="fixed top-14 left-0 w-64 h-full bg-gray-900 text-white transform -translate-x-full transition-transform duration-300 ease-in-out z-40"> diff --git a/nginx/conf/locations/app.conf b/nginx/conf/locations/app.conf index 5554a40..75bca27 100644 --- a/nginx/conf/locations/app.conf +++ b/nginx/conf/locations/app.conf @@ -3,6 +3,10 @@ location /app { try_files /index.html =404; } -location /assets { +location /assets { root /volumes/static/app/; } + +location / { + return 301 https://$http_host/app; +} From 00e4f522abd987da11ff028be01ff39781bf3556 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Sat, 6 Dec 2025 19:07:13 +0100 Subject: [PATCH 53/66] feat(frontend/otp): allow changing otp status and showing qrcode --- frontend/package.json | 2 + frontend/pnpm-lock.yaml | 255 +++++++++++++++++++++++- frontend/src/pages/profile/profile.html | 7 +- frontend/src/pages/profile/profile.ts | 154 +++++++++----- 4 files changed, 359 insertions(+), 59 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index b0fc5a2..a25c3ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.1.17", + "@types/qrcode": "^1.5.6", "js-cookie": "^3.0.5", + "qrcode": "^1.5.4", "socket.io-client": "^4.8.1", "tailwindcss": "^4.1.17" } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ac3aea5..4ca399c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,10 +10,16 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)) + version: 4.1.17(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)) + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 js-cookie: specifier: ^3.0.5 version: 3.0.5 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 socket.io-client: specifier: ^4.8.1 version: 4.8.1 @@ -29,10 +35,10 @@ importers: version: 5.9.3 vite: specifier: ^7.2.7 - version: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) + version: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)) packages: @@ -417,6 +423,34 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -435,10 +469,20 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + engine.io-client@6.6.3: resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} @@ -464,17 +508,29 @@ packages: picomatch: optional: true + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -553,6 +609,10 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -564,6 +624,22 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -571,15 +647,34 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + socket.io-client@4.8.1: resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} engines: {node: '>=10.0.0'} @@ -592,6 +687,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + tailwindcss@4.1.17: resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} @@ -618,6 +721,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -666,6 +772,13 @@ packages: yaml: optional: true + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + ws@8.17.1: resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} engines: {node: '>=10.0.0'} @@ -682,6 +795,17 @@ packages: resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} engines: {node: '>=0.4.0'} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + snapshots: '@esbuild/aix-ppc64@0.25.12': @@ -910,17 +1034,45 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.1.17(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2) '@types/estree@1.0.8': {} '@types/js-cookie@3.0.6': {} + '@types/node@24.10.2': + dependencies: + undici-types: 7.16.0 + + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 24.10.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + camelcase@5.3.1: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + debug@4.3.7: dependencies: ms: 2.1.3 @@ -929,8 +1081,14 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + detect-libc@2.1.2: {} + dijkstrajs@1.0.3: {} + + emoji-regex@8.0.0: {} + engine.io-client@6.6.3: dependencies: '@socket.io/component-emitter': 3.1.2 @@ -983,13 +1141,22 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + fsevents@2.3.3: optional: true + get-caller-file@2.0.5: {} + globrex@0.1.2: {} graceful-fs@4.2.11: {} + is-fullwidth-code-point@3.0.0: {} + jiti@2.6.1: {} js-cookie@3.0.5: {} @@ -1043,6 +1210,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1051,16 +1222,40 @@ snapshots: nanoid@3.3.11: {} + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + path-exists@4.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} + pngjs@5.0.0: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -1089,6 +1284,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 + set-blocking@2.0.0: {} + socket.io-client@4.8.1: dependencies: '@socket.io/component-emitter': 3.1.2 @@ -1109,6 +1306,16 @@ snapshots: source-map-js@1.2.1: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + tailwindcss@4.1.17: {} tapable@2.3.0: {} @@ -1124,18 +1331,20 @@ snapshots: typescript@5.9.3: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2)): + undici-types@7.16.0: {} + + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.7(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.7(jiti@2.6.1)(lightningcss@1.30.2): + vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -1144,10 +1353,40 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.10.2 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 + which-module@2.0.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + ws@8.17.1: {} xmlhttprequest-ssl@2.1.2: {} + + y18n@4.0.3: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html index 1c56014..1b2d180 100644 --- a/frontend/src/pages/profile/profile.html +++ b/frontend/src/pages/profile/profile.html @@ -59,7 +59,12 @@ - + diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts index b3d6e77..c6a7bb0 100644 --- a/frontend/src/pages/profile/profile.ts +++ b/frontend/src/pages/profile/profile.ts @@ -1,109 +1,164 @@ import { addRoute, navigateTo, setTitle } from "@app/routing"; import { showError } from "@app/toast"; -import page from './profile.html?raw' +import page from "./profile.html?raw"; import { updateUser } from "@app/auth"; import { isNullish } from "@app/utils"; import client from "@app/api"; +import QRCode from "qrcode"; +type OAuthQRCodeOptions = { + label?: string; // e.g. your-app:user@example.com + issuer?: string; // e.g. "YourApp" + algorithm?: "SHA1" | "SHA256" | "SHA512"; + digits?: number; + period?: number; +}; + +/** + * Renders an OAuth2-compatible TOTP QR code into a canvas. + * + * @param canvas HTMLCanvasElement to draw into + * @param secret Base32-encoded shared secret + * @param options Meta data for QR (label, issuer, etc.) + */ +export async function renderOAuth2QRCode( + canvas: HTMLCanvasElement, + secret: string, +): Promise { + // Encode the otpauth:// URL + const otpauthUrl = new URL(`otpauth://totp/ft_boule:totp`); + + otpauthUrl.searchParams.set("secret", secret.replace(/=+$/, "")); + otpauthUrl.searchParams.set("issuer", "ft_boule"); + + // Render QR code into the canvas + await QRCode.toCanvas(canvas, otpauthUrl.toString(), { + margin: 1, + scale: 5, + }); + canvas.style.width = ""; + canvas.style.height = ""; +} async function route(url: string, _args: { [k: string]: string }) { - setTitle('Edit Profile') + setTitle("Edit Profile"); return { - html: page, postInsert: async (app: HTMLElement | undefined) => { + html: page, + postInsert: async (app: HTMLElement | undefined) => { const user = await updateUser(); - if (isNullish(user)) - return showError('No User'); - if (isNullish(app)) - return showError('Failed to render'); + if (isNullish(user)) return showError("No User"); + if (isNullish(app)) return showError("Failed to render"); let totpState = await (async () => { let res = await client.statusOtp(); if (res.kind === "success") return { - enabled: (res.msg as string) === "statusOtp.success.enabled", - secret: ((res.msg as string) === "statusOtp.success.enabled") ? res.payload.secret : null, + enabled: + (res.msg as string) === "statusOtp.success.enabled", + secret: + (res.msg as string) === "statusOtp.success.enabled" + ? res.payload.secret + : null, }; else { - showError('Failed to get OTP status') + showError("Failed to get OTP status"); return { - enabled: false, secret: null, - } + enabled: false, + secret: null, + }; } - - })() + })(); // ---- Simulated State ---- let totpEnabled = totpState.enabled; let totpSecret = totpState.secret; // would come from backend let guestBox = app.querySelector("#isGuestBox")!; - let displayNameWrapper = app.querySelector("#displayNameWrapper")!; - let displayNameBox = app.querySelector("#displayNameBox")!; - let displayNameButton = app.querySelector("#displayNameButton")!; - let loginNameWrapper = app.querySelector("#loginNameWrapper")!; - let loginNameBox = app.querySelector("#loginNameBox")!; - let passwordWrapper = app.querySelector("#passwordWrapper")!; - let passwordBox = app.querySelector("#passwordBox")!; - let passwordButton = app.querySelector("#passwordButton")!; - + let displayNameWrapper = app.querySelector( + "#displayNameWrapper", + )!; + let displayNameBox = + app.querySelector("#displayNameBox")!; + let displayNameButton = + app.querySelector("#displayNameButton")!; + let loginNameWrapper = + app.querySelector("#loginNameWrapper")!; + let loginNameBox = + app.querySelector("#loginNameBox")!; + let passwordWrapper = + app.querySelector("#passwordWrapper")!; + let passwordBox = + app.querySelector("#passwordBox")!; + let passwordButton = + app.querySelector("#passwordButton")!; if (!isNullish(user.selfInfo?.loginName)) loginNameBox.innerText = user.selfInfo?.loginName; else - loginNameBox.innerHTML = 'You don\'t have a login name'; + loginNameBox.innerHTML = + 'You don\'t have a login name'; displayNameBox.value = user.name; guestBox.hidden = !user.guest; // ---- DOM Elements ---- const totpStatusText = app.querySelector("#totpStatusText")!; - const enableBtn = app.querySelector("#enableTotp")!; - const disableBtn = app.querySelector("#disableTotp")!; - const showSecretBtn = app.querySelector("#showSecret")!; + const enableBtn = + app.querySelector("#enableTotp")!; + const disableBtn = + app.querySelector("#disableTotp")!; + const showSecretBtn = + app.querySelector("#showSecret")!; const secretBox = app.querySelector("#totpSecretBox")!; + const secretText = + app.querySelector("#totpSecretText")!; + const secretCanvas = + app.querySelector("#totpSecretCanvas")!; if (user.guest) { for (let c of passwordButton.classList.values()) { - if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) passwordButton.classList.remove(c); } passwordButton.disabled = true; - passwordButton.classList.add('bg-gray-700', 'hover:bg-gray-700'); + passwordButton.classList.add( + "bg-gray-700", + "hover:bg-gray-700", + ); passwordBox.disabled = true; - passwordBox.classList.add('color-white'); + passwordBox.classList.add("color-white"); for (let c of displayNameButton.classList.values()) { - if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) displayNameButton.classList.remove(c); } displayNameButton.disabled = true; - displayNameButton.classList.add('bg-gray-700'); - displayNameButton.classList.add('color-white'); + displayNameButton.classList.add("bg-gray-700"); + displayNameButton.classList.add("color-white"); displayNameBox.disabled = true; - displayNameBox.classList.add('color-white'); + displayNameBox.classList.add("color-white"); for (let c of enableBtn.classList.values()) { - if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) enableBtn.classList.remove(c); } for (let c of disableBtn.classList.values()) { - if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) disableBtn.classList.remove(c); } for (let c of showSecretBtn.classList.values()) { - if (c.startsWith('bg-') || c.startsWith('hover:bg-')) + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) showSecretBtn.classList.remove(c); } - enableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); - disableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); - showSecretBtn.classList.add('bg-gray-700', 'hover:bg-gray-700'); + enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); enableBtn.disabled = true; disableBtn.disabled = true; showSecretBtn.disabled = true; } - // ---- Update UI ---- function refreshTotpUI() { if (totpEnabled) { @@ -127,8 +182,7 @@ async function route(url: string, _args: { [k: string]: string }) { let res = await client.enableOtp(); if (res.kind === "success") { navigateTo(url); - } - else { + } else { showError(`failed to activate OTP: ${res.msg}`); } }; @@ -137,23 +191,23 @@ async function route(url: string, _args: { [k: string]: string }) { let res = await client.disableOtp(); if (res.kind === "success") { navigateTo(url); - } - else { + } else { showError(`failed to deactivate OTP: ${res.msg}`); } }; showSecretBtn.onclick = () => { - secretBox.textContent = `TOTP Secret: ${totpSecret}`; + if (!isNullish(totpSecret)) { + secretText.textContent = totpSecret; + renderOAuth2QRCode(secretCanvas, totpSecret); + } secretBox.classList.toggle("hidden"); }; // Initialize UI state refreshTotpUI(); - } + }, }; } - - -addRoute('/profile', route) +addRoute("/profile", route); From 37a33d8a7367b9c2a75a91d6c12c8801e5d91c67 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Wed, 10 Dec 2025 16:38:26 +0100 Subject: [PATCH 54/66] yes --- frontend/Dockerfile | 16 +- .../api/generated/.openapi-generator/FILES | 5 + .../src/api/generated/apis/OpenapiOtherApi.ts | 94 ++++- .../models/ChangeDisplayName200Response.ts | 93 +++++ .../models/ChangeDisplayName400Response.ts | 94 +++++ .../models/ChangeDisplayNameRequest.ts | 66 +++ .../generated/models/GuestLogin400Response.ts | 93 +++++ .../api/generated/models/GuestLoginRequest.ts | 65 +++ frontend/src/api/generated/models/index.ts | 5 + frontend/src/auth/index.ts | 4 +- frontend/src/pages/login/login.ts | 2 +- frontend/src/pages/profile/profile.html | 27 +- frontend/src/pages/profile/profile.ts | 379 ++++++++++-------- src/@shared/src/database/init.dbml | 2 +- src/@shared/src/database/init.sql | 2 +- src/@shared/src/database/mixin/user.ts | 33 +- src/auth/openapi.json | 40 ++ src/auth/src/routes/guestLogin.ts | 80 +++- src/auth/src/routes/oauth2/callback.ts | 16 +- src/auth/src/routes/signin.ts | 14 +- src/openapi.json | 151 +++++++ src/user/openapi.json | 108 +++++ src/user/src/routes/changeDisplayName.ts | 44 ++ src/user/src/routes/info.ts | 2 +- 24 files changed, 1233 insertions(+), 202 deletions(-) create mode 100644 frontend/src/api/generated/models/ChangeDisplayName200Response.ts create mode 100644 frontend/src/api/generated/models/ChangeDisplayName400Response.ts create mode 100644 frontend/src/api/generated/models/ChangeDisplayNameRequest.ts create mode 100644 frontend/src/api/generated/models/GuestLogin400Response.ts create mode 100644 frontend/src/api/generated/models/GuestLoginRequest.ts create mode 100644 src/user/src/routes/changeDisplayName.ts diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d772c9e..f44fde2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,15 +1,23 @@ FROM node:22-alpine AS pnpm_base RUN npm install --global pnpm@10; +FROM pnpm_base AS deps + +COPY ./package.json ./pnpm-lock.yaml ./pnpm-workspace.yaml /src/ +WORKDIR /src +RUN pnpm install --frozen-lockfile; + FROM pnpm_base AS builder -COPY . /src WORKDIR /src -RUN pnpm install --frozen-lockfile && pnpm run build; +COPY --from=deps /src/node_modules /src/node_modules +COPY . /src -FROM node:22-alpine +RUN pnpm run build; -COPY --from=builder /src/dist /dist +FROM pnpm_base + +COPY --from=builder /src/dist /dist COPY ./run.sh /bin/run.sh RUN chmod +x /bin/run.sh diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index b05ee98..ed23b0c 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -1,6 +1,9 @@ apis/OpenapiOtherApi.ts apis/index.ts index.ts +models/ChangeDisplayName200Response.ts +models/ChangeDisplayName400Response.ts +models/ChangeDisplayNameRequest.ts models/ChatTest200Response.ts models/ChatTest200ResponsePayload.ts models/DisableOtp200Response.ts @@ -20,7 +23,9 @@ models/GetUser404Response.ts models/GetUserUserParameter.ts models/GuestLogin200Response.ts models/GuestLogin200ResponsePayload.ts +models/GuestLogin400Response.ts models/GuestLogin500Response.ts +models/GuestLoginRequest.ts models/Login200Response.ts models/Login202Response.ts models/Login202ResponsePayload.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index c37580a..9609421 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -15,6 +15,9 @@ import * as runtime from '../runtime'; import type { + ChangeDisplayName200Response, + ChangeDisplayName400Response, + ChangeDisplayNameRequest, ChatTest200Response, DisableOtp200Response, DisableOtp400Response, @@ -28,7 +31,9 @@ import type { GetUser404Response, GetUserUserParameter, GuestLogin200Response, + GuestLogin400Response, GuestLogin500Response, + GuestLoginRequest, Login200Response, Login202Response, Login400Response, @@ -49,6 +54,12 @@ import type { StatusOtp500Response, } from '../models/index'; import { + ChangeDisplayName200ResponseFromJSON, + ChangeDisplayName200ResponseToJSON, + ChangeDisplayName400ResponseFromJSON, + ChangeDisplayName400ResponseToJSON, + ChangeDisplayNameRequestFromJSON, + ChangeDisplayNameRequestToJSON, ChatTest200ResponseFromJSON, ChatTest200ResponseToJSON, DisableOtp200ResponseFromJSON, @@ -75,8 +86,12 @@ import { GetUserUserParameterToJSON, GuestLogin200ResponseFromJSON, GuestLogin200ResponseToJSON, + GuestLogin400ResponseFromJSON, + GuestLogin400ResponseToJSON, GuestLogin500ResponseFromJSON, GuestLogin500ResponseToJSON, + GuestLoginRequestFromJSON, + GuestLoginRequestToJSON, Login200ResponseFromJSON, Login200ResponseToJSON, Login202ResponseFromJSON, @@ -115,10 +130,18 @@ import { StatusOtp500ResponseToJSON, } from '../models/index'; +export interface ChangeDisplayNameOperationRequest { + changeDisplayNameRequest: ChangeDisplayNameRequest; +} + export interface GetUserRequest { user: GetUserUserParameter; } +export interface GuestLoginOperationRequest { + guestLoginRequest?: GuestLoginRequest; +} + export interface LoginOperationRequest { loginRequest: LoginRequest; } @@ -136,6 +159,62 @@ export interface SigninRequest { */ export class OpenapiOtherApi extends runtime.BaseAPI { + /** + */ + async changeDisplayNameRaw(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['changeDisplayNameRequest'] == null) { + throw new runtime.RequiredError( + 'changeDisplayNameRequest', + 'Required parameter "changeDisplayNameRequest" was null or undefined when calling changeDisplayName().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + + let urlPath = `/api/user/changeDisplayName`; + + const response = await this.request({ + path: urlPath, + method: 'PUT', + headers: headerParameters, + query: queryParameters, + body: ChangeDisplayNameRequestToJSON(requestParameters['changeDisplayNameRequest']), + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangeDisplayName200ResponseFromJSON(jsonValue)); + } + if (response.status === 400) { + // Object response for status 400 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangeDisplayName400ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401`); + } + + /** + */ + async changeDisplayName(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.changeDisplayNameRaw(requestParameters, initOverrides); + return await response.value(); + } + /** */ async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -334,11 +413,13 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async guestLoginRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async guestLoginRaw(requestParameters: GuestLoginOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; + headerParameters['Content-Type'] = 'application/json'; + let urlPath = `/api/auth/guest`; @@ -347,6 +428,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { method: 'POST', headers: headerParameters, query: queryParameters, + body: GuestLoginRequestToJSON(requestParameters['guestLoginRequest']), }, initOverrides); // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses @@ -357,6 +439,10 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // Object response for status 200 return new runtime.JSONApiResponse(response, (jsonValue) => GuestLogin200ResponseFromJSON(jsonValue)); } + if (response.status === 400) { + // Object response for status 400 + return new runtime.JSONApiResponse(response, (jsonValue) => GuestLogin400ResponseFromJSON(jsonValue)); + } if (response.status === 500) { // Object response for status 500 return new runtime.JSONApiResponse(response, (jsonValue) => GuestLogin500ResponseFromJSON(jsonValue)); @@ -364,13 +450,13 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes // Only throw if responses were defined but none matched the actual status code - throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 500`); + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 500`); } /** */ - async guestLogin(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.guestLoginRaw(initOverrides); + async guestLogin(requestParameters: GuestLoginOperationRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.guestLoginRaw(requestParameters, initOverrides); return await response.value(); } diff --git a/frontend/src/api/generated/models/ChangeDisplayName200Response.ts b/frontend/src/api/generated/models/ChangeDisplayName200Response.ts new file mode 100644 index 0000000..63168b7 --- /dev/null +++ b/frontend/src/api/generated/models/ChangeDisplayName200Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangeDisplayName200Response + */ +export interface ChangeDisplayName200Response { + /** + * + * @type {string} + * @memberof ChangeDisplayName200Response + */ + kind: ChangeDisplayName200ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangeDisplayName200Response + */ + msg: ChangeDisplayName200ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangeDisplayName200ResponseKindEnum = { + Success: 'success' +} as const; +export type ChangeDisplayName200ResponseKindEnum = typeof ChangeDisplayName200ResponseKindEnum[keyof typeof ChangeDisplayName200ResponseKindEnum]; + +/** + * @export + */ +export const ChangeDisplayName200ResponseMsgEnum = { + ChangeDisplayNameSuccess: 'changeDisplayName.success' +} as const; +export type ChangeDisplayName200ResponseMsgEnum = typeof ChangeDisplayName200ResponseMsgEnum[keyof typeof ChangeDisplayName200ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangeDisplayName200Response interface. + */ +export function instanceOfChangeDisplayName200Response(value: object): value is ChangeDisplayName200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangeDisplayName200ResponseFromJSON(json: any): ChangeDisplayName200Response { + return ChangeDisplayName200ResponseFromJSONTyped(json, false); +} + +export function ChangeDisplayName200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangeDisplayName200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangeDisplayName200ResponseToJSON(json: any): ChangeDisplayName200Response { + return ChangeDisplayName200ResponseToJSONTyped(json, false); +} + +export function ChangeDisplayName200ResponseToJSONTyped(value?: ChangeDisplayName200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangeDisplayName400Response.ts b/frontend/src/api/generated/models/ChangeDisplayName400Response.ts new file mode 100644 index 0000000..1487962 --- /dev/null +++ b/frontend/src/api/generated/models/ChangeDisplayName400Response.ts @@ -0,0 +1,94 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangeDisplayName400Response + */ +export interface ChangeDisplayName400Response { + /** + * + * @type {string} + * @memberof ChangeDisplayName400Response + */ + kind: ChangeDisplayName400ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangeDisplayName400Response + */ + msg: ChangeDisplayName400ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangeDisplayName400ResponseKindEnum = { + Failure: 'failure' +} as const; +export type ChangeDisplayName400ResponseKindEnum = typeof ChangeDisplayName400ResponseKindEnum[keyof typeof ChangeDisplayName400ResponseKindEnum]; + +/** + * @export + */ +export const ChangeDisplayName400ResponseMsgEnum = { + ChangeDisplayNameAlreadyExist: 'changeDisplayName.alreadyExist', + ChangeDisplayNameInvalid: 'changeDisplayName.invalid' +} as const; +export type ChangeDisplayName400ResponseMsgEnum = typeof ChangeDisplayName400ResponseMsgEnum[keyof typeof ChangeDisplayName400ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangeDisplayName400Response interface. + */ +export function instanceOfChangeDisplayName400Response(value: object): value is ChangeDisplayName400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangeDisplayName400ResponseFromJSON(json: any): ChangeDisplayName400Response { + return ChangeDisplayName400ResponseFromJSONTyped(json, false); +} + +export function ChangeDisplayName400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangeDisplayName400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangeDisplayName400ResponseToJSON(json: any): ChangeDisplayName400Response { + return ChangeDisplayName400ResponseToJSONTyped(json, false); +} + +export function ChangeDisplayName400ResponseToJSONTyped(value?: ChangeDisplayName400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangeDisplayNameRequest.ts b/frontend/src/api/generated/models/ChangeDisplayNameRequest.ts new file mode 100644 index 0000000..c72b7bb --- /dev/null +++ b/frontend/src/api/generated/models/ChangeDisplayNameRequest.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangeDisplayNameRequest + */ +export interface ChangeDisplayNameRequest { + /** + * New Display Name + * @type {string} + * @memberof ChangeDisplayNameRequest + */ + name: string; +} + +/** + * Check if a given object implements the ChangeDisplayNameRequest interface. + */ +export function instanceOfChangeDisplayNameRequest(value: object): value is ChangeDisplayNameRequest { + if (!('name' in value) || value['name'] === undefined) return false; + return true; +} + +export function ChangeDisplayNameRequestFromJSON(json: any): ChangeDisplayNameRequest { + return ChangeDisplayNameRequestFromJSONTyped(json, false); +} + +export function ChangeDisplayNameRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangeDisplayNameRequest { + if (json == null) { + return json; + } + return { + + 'name': json['name'], + }; +} + +export function ChangeDisplayNameRequestToJSON(json: any): ChangeDisplayNameRequest { + return ChangeDisplayNameRequestToJSONTyped(json, false); +} + +export function ChangeDisplayNameRequestToJSONTyped(value?: ChangeDisplayNameRequest | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'name': value['name'], + }; +} + diff --git a/frontend/src/api/generated/models/GuestLogin400Response.ts b/frontend/src/api/generated/models/GuestLogin400Response.ts new file mode 100644 index 0000000..8f4c1ce --- /dev/null +++ b/frontend/src/api/generated/models/GuestLogin400Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface GuestLogin400Response + */ +export interface GuestLogin400Response { + /** + * + * @type {string} + * @memberof GuestLogin400Response + */ + kind: GuestLogin400ResponseKindEnum; + /** + * + * @type {string} + * @memberof GuestLogin400Response + */ + msg: GuestLogin400ResponseMsgEnum; +} + + +/** + * @export + */ +export const GuestLogin400ResponseKindEnum = { + Failed: 'failed' +} as const; +export type GuestLogin400ResponseKindEnum = typeof GuestLogin400ResponseKindEnum[keyof typeof GuestLogin400ResponseKindEnum]; + +/** + * @export + */ +export const GuestLogin400ResponseMsgEnum = { + GuestLoginFailedInvalid: 'guestLogin.failed.invalid' +} as const; +export type GuestLogin400ResponseMsgEnum = typeof GuestLogin400ResponseMsgEnum[keyof typeof GuestLogin400ResponseMsgEnum]; + + +/** + * Check if a given object implements the GuestLogin400Response interface. + */ +export function instanceOfGuestLogin400Response(value: object): value is GuestLogin400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function GuestLogin400ResponseFromJSON(json: any): GuestLogin400Response { + return GuestLogin400ResponseFromJSONTyped(json, false); +} + +export function GuestLogin400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): GuestLogin400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function GuestLogin400ResponseToJSON(json: any): GuestLogin400Response { + return GuestLogin400ResponseToJSONTyped(json, false); +} + +export function GuestLogin400ResponseToJSONTyped(value?: GuestLogin400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/GuestLoginRequest.ts b/frontend/src/api/generated/models/GuestLoginRequest.ts new file mode 100644 index 0000000..15aa94b --- /dev/null +++ b/frontend/src/api/generated/models/GuestLoginRequest.ts @@ -0,0 +1,65 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface GuestLoginRequest + */ +export interface GuestLoginRequest { + /** + * + * @type {string} + * @memberof GuestLoginRequest + */ + name?: string; +} + +/** + * Check if a given object implements the GuestLoginRequest interface. + */ +export function instanceOfGuestLoginRequest(value: object): value is GuestLoginRequest { + return true; +} + +export function GuestLoginRequestFromJSON(json: any): GuestLoginRequest { + return GuestLoginRequestFromJSONTyped(json, false); +} + +export function GuestLoginRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): GuestLoginRequest { + if (json == null) { + return json; + } + return { + + 'name': json['name'] == null ? undefined : json['name'], + }; +} + +export function GuestLoginRequestToJSON(json: any): GuestLoginRequest { + return GuestLoginRequestToJSONTyped(json, false); +} + +export function GuestLoginRequestToJSONTyped(value?: GuestLoginRequest | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'name': value['name'], + }; +} + diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index a3cdb3a..f00b24e 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -1,5 +1,8 @@ /* tslint:disable */ /* eslint-disable */ +export * from './ChangeDisplayName200Response'; +export * from './ChangeDisplayName400Response'; +export * from './ChangeDisplayNameRequest'; export * from './ChatTest200Response'; export * from './ChatTest200ResponsePayload'; export * from './DisableOtp200Response'; @@ -19,7 +22,9 @@ export * from './GetUser404Response'; export * from './GetUserUserParameter'; export * from './GuestLogin200Response'; export * from './GuestLogin200ResponsePayload'; +export * from './GuestLogin400Response'; export * from './GuestLogin500Response'; +export * from './GuestLoginRequest'; export * from './Login200Response'; export * from './Login202Response'; export * from './Login202ResponsePayload'; diff --git a/frontend/src/auth/index.ts b/frontend/src/auth/index.ts index 7807bf9..449dabc 100644 --- a/frontend/src/auth/index.ts +++ b/frontend/src/auth/index.ts @@ -7,8 +7,8 @@ export type User = { name: string; selfInfo?: { loginName?: string; - provider_id?: string; - provider_user?: string; + providerId?: string; + providerUser?: string; } }; diff --git a/frontend/src/pages/login/login.ts b/frontend/src/pages/login/login.ts index 2cd0a14..5eb5123 100644 --- a/frontend/src/pages/login/login.ts +++ b/frontend/src/pages/login/login.ts @@ -209,7 +209,7 @@ async function handleLogin( document.querySelector("#bGuestLogin"); bLoginAsGuest?.addEventListener("click", async () => { try { - const res = await client.guestLogin(); + const res = await client.guestLogin({ guestLoginRequest: { name: undefined } }); switch (res.kind) { case "success": { Cookie.set("token", res.payload.token, { diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html index 1b2d180..6752e23 100644 --- a/frontend/src/pages/profile/profile.html +++ b/frontend/src/pages/profile/profile.html @@ -7,12 +7,33 @@ You can't change anything here +
+ + +
+ + -
+ - + +
@@ -35,7 +56,7 @@
-
+

Two-Factor Authentication (TOTP)

diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts index c6a7bb0..39171ce 100644 --- a/frontend/src/pages/profile/profile.ts +++ b/frontend/src/pages/profile/profile.ts @@ -1,5 +1,5 @@ -import { addRoute, navigateTo, setTitle } from "@app/routing"; -import { showError } from "@app/toast"; +import { addRoute, handleRoute, navigateTo, setTitle } from "@app/routing"; +import { showError, showSuccess } from "@app/toast"; import page from "./profile.html?raw"; import { updateUser } from "@app/auth"; import { isNullish } from "@app/utils"; @@ -38,176 +38,219 @@ export async function renderOAuth2QRCode( }); canvas.style.width = ""; canvas.style.height = ""; -} - -async function route(url: string, _args: { [k: string]: string }) { - setTitle("Edit Profile"); - return { - html: page, - postInsert: async (app: HTMLElement | undefined) => { - const user = await updateUser(); - if (isNullish(user)) return showError("No User"); - if (isNullish(app)) return showError("Failed to render"); - let totpState = await (async () => { - let res = await client.statusOtp(); - if (res.kind === "success") - return { - enabled: - (res.msg as string) === "statusOtp.success.enabled", - secret: - (res.msg as string) === "statusOtp.success.enabled" - ? res.payload.secret - : null, - }; - else { - showError("Failed to get OTP status"); - return { - enabled: false, - secret: null, - }; - } - })(); - // ---- Simulated State ---- - let totpEnabled = totpState.enabled; - let totpSecret = totpState.secret; // would come from backend - - let guestBox = app.querySelector("#isGuestBox")!; - let displayNameWrapper = app.querySelector( - "#displayNameWrapper", - )!; - let displayNameBox = - app.querySelector("#displayNameBox")!; - let displayNameButton = - app.querySelector("#displayNameButton")!; - let loginNameWrapper = - app.querySelector("#loginNameWrapper")!; - let loginNameBox = - app.querySelector("#loginNameBox")!; - let passwordWrapper = - app.querySelector("#passwordWrapper")!; - let passwordBox = - app.querySelector("#passwordBox")!; - let passwordButton = - app.querySelector("#passwordButton")!; - - if (!isNullish(user.selfInfo?.loginName)) - loginNameBox.innerText = user.selfInfo?.loginName; - else - loginNameBox.innerHTML = - 'You don\'t have a login name'; - displayNameBox.value = user.name; - - guestBox.hidden = !user.guest; - - // ---- DOM Elements ---- - const totpStatusText = app.querySelector("#totpStatusText")!; - const enableBtn = - app.querySelector("#enableTotp")!; - const disableBtn = - app.querySelector("#disableTotp")!; - const showSecretBtn = - app.querySelector("#showSecret")!; - const secretBox = app.querySelector("#totpSecretBox")!; - const secretText = - app.querySelector("#totpSecretText")!; - const secretCanvas = - app.querySelector("#totpSecretCanvas")!; - - if (user.guest) { - for (let c of passwordButton.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - passwordButton.classList.remove(c); - } - passwordButton.disabled = true; - passwordButton.classList.add( - "bg-gray-700", - "hover:bg-gray-700", - ); - - passwordBox.disabled = true; - passwordBox.classList.add("color-white"); - - for (let c of displayNameButton.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - displayNameButton.classList.remove(c); - } - displayNameButton.disabled = true; - displayNameButton.classList.add("bg-gray-700"); - displayNameButton.classList.add("color-white"); - - displayNameBox.disabled = true; - displayNameBox.classList.add("color-white"); - - for (let c of enableBtn.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - enableBtn.classList.remove(c); - } - for (let c of disableBtn.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - disableBtn.classList.remove(c); - } - for (let c of showSecretBtn.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - showSecretBtn.classList.remove(c); - } - enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - - enableBtn.disabled = true; - disableBtn.disabled = true; - showSecretBtn.disabled = true; + function removeBgColor(...elem: HTMLElement[]) { + for (let e of elem) { + for (let c of e.classList.values()) { + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) + e.classList.remove(c); } + } + } - // ---- Update UI ---- - function refreshTotpUI() { - if (totpEnabled) { - totpStatusText.textContent = "Status: Enabled"; + async function route(url: string, _args: { [k: string]: string }) { + setTitle("Edit Profile"); + return { + html: page, + postInsert: async (app: HTMLElement | undefined) => { + const user = await updateUser(); + if (isNullish(user)) return showError("No User"); + if (isNullish(app)) return showError("Failed to render"); + let totpState = await (async () => { + let res = await client.statusOtp(); + if (res.kind === "success") + return { + enabled: + (res.msg as string) === "statusOtp.success.enabled", + secret: + (res.msg as string) === "statusOtp.success.enabled" + ? res.payload.secret + : null, + }; + else { + showError("Failed to get OTP status"); + return { + enabled: false, + secret: null, + }; + } + })(); + // ---- Simulated State ---- + let totpEnabled = totpState.enabled; + let totpSecret = totpState.secret; // would come from backend - enableBtn.classList.add("hidden"); - disableBtn.classList.remove("hidden"); - showSecretBtn.classList.remove("hidden"); - } else { - totpStatusText.textContent = "Status: Disabled"; + let guestBox = app.querySelector("#isGuestBox")!; + let displayNameWrapper = app.querySelector( + "#displayNameWrapper", + )!; + let displayNameBox = + app.querySelector("#displayNameBox")!; + let displayNameButton = + app.querySelector("#displayNameButton")!; + let loginNameWrapper = + app.querySelector("#loginNameWrapper")!; + let loginNameBox = + app.querySelector("#loginNameBox")!; + let passwordWrapper = + app.querySelector("#passwordWrapper")!; + let passwordBox = + app.querySelector("#passwordBox")!; + let passwordButton = + app.querySelector("#passwordButton")!; - enableBtn.classList.remove("hidden"); - disableBtn.classList.add("hidden"); - showSecretBtn.classList.add("hidden"); - secretBox.classList.add("hidden"); - } - } + let providerWrapper = + app.querySelector("#providerWrapper")!; + let providerNameBox = + app.querySelector("#providerNameBox")!; + let providerUserBox = + app.querySelector("#providerUserBox")!; - // ---- Button Events ---- - enableBtn.onclick = async () => { - let res = await client.enableOtp(); - if (res.kind === "success") { - navigateTo(url); - } else { - showError(`failed to activate OTP: ${res.msg}`); - } + let accountTypeBox = + app.querySelector("#accountType")!; + displayNameBox.value = user.name; + + guestBox.hidden = !user.guest; + + // ---- DOM Elements ---- + const totpStatusText = app.querySelector("#totpStatusText")!; + const enableBtn = + app.querySelector("#enableTotp")!; + const disableBtn = + app.querySelector("#disableTotp")!; + const showSecretBtn = + app.querySelector("#showSecret")!; + const secretBox = app.querySelector("#totpSecretBox")!; + const secretText = + app.querySelector("#totpSecretText")!; + const secretCanvas = + app.querySelector("#totpSecretCanvas")!; + + if (user.guest) { + for (let c of passwordButton.classList.values()) { + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) + passwordButton.classList.remove(c); + } + let totpWrapper = app.querySelector("#totpWrapper")!; + + if (user.guest) { + removeBgColor( + passwordButton, + displayNameButton, + enableBtn, + disableBtn, + showSecretBtn, + ); + + passwordButton.classList.add( + "bg-gray-700", + "hover:bg-gray-700", + ); + + passwordBox.disabled = true; + passwordBox.classList.add("color-white"); + + displayNameButton.disabled = true; + displayNameButton.classList.add("bg-gray-700", "color-white"); + + displayNameBox.disabled = true; + displayNameBox.classList.add("color-white"); + enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + + enableBtn.disabled = true; + disableBtn.disabled = true; + showSecretBtn.disabled = true; + + accountTypeBox.innerText = "Guest"; + } else if (!isNullish(user.selfInfo?.loginName)) { + loginNameWrapper.hidden = false; + loginNameBox.innerText = user.selfInfo.loginName; + + accountTypeBox.innerText = "Normal"; + } else if ( + !isNullish(user.selfInfo?.providerId) && + !isNullish(user.selfInfo?.providerUser) + ) { + providerWrapper.hidden = false; + providerNameBox.innerText = user.selfInfo.providerId; + providerUserBox.innerText = user.selfInfo.providerUser; + + enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + + enableBtn.disabled = true; + disableBtn.disabled = true; + showSecretBtn.disabled = true; + + removeBgColor(enableBtn, disableBtn, showSecretBtn); + passwordWrapper.hidden = true; + totpWrapper.hidden = true; + + accountTypeBox.innerText = "Provider"; + } + + // ---- Update UI ---- + function refreshTotpUI() { + if (totpEnabled) { + totpStatusText.textContent = "Status: Enabled"; + + enableBtn.classList.add("hidden"); + disableBtn.classList.remove("hidden"); + showSecretBtn.classList.remove("hidden"); + } else { + totpStatusText.textContent = "Status: Disabled"; + + enableBtn.classList.remove("hidden"); + disableBtn.classList.add("hidden"); + showSecretBtn.classList.add("hidden"); + secretBox.classList.add("hidden"); + } + } + + // ---- Button Events ---- + enableBtn.onclick = async () => { + let res = await client.enableOtp(); + if (res.kind === "success") { + navigateTo(url); + } else { + showError(`failed to activate OTP: ${res.msg}`); + } + }; + + disableBtn.onclick = async () => { + let res = await client.disableOtp(); + if (res.kind === "success") { + navigateTo(url); + } else { + showError(`failed to deactivate OTP: ${res.msg}`); + } + }; + + showSecretBtn.onclick = () => { + if (!isNullish(totpSecret)) { + secretText.textContent = totpSecret; + renderOAuth2QRCode(secretCanvas, totpSecret); + } + secretBox.classList.toggle("hidden"); + }; + + displayNameButton.onclick = async () => { + let req = await client.changeDisplayName({ + changeDisplayNameRequest: { name: displayNameBox.value }, + }); + if (req.kind === "success") { + showSuccess("Successfully changed display name"); + handleRoute(); + } else { + showError(`Failed to update: ${req.msg}`); + } + }; + + // Initialize UI state + refreshTotpUI(); + }, }; + } - disableBtn.onclick = async () => { - let res = await client.disableOtp(); - if (res.kind === "success") { - navigateTo(url); - } else { - showError(`failed to deactivate OTP: ${res.msg}`); - } - }; - - showSecretBtn.onclick = () => { - if (!isNullish(totpSecret)) { - secretText.textContent = totpSecret; - renderOAuth2QRCode(secretCanvas, totpSecret); - } - secretBox.classList.toggle("hidden"); - }; - - // Initialize UI state - refreshTotpUI(); - }, - }; -} - -addRoute("/profile", route); + addRoute("/profile", route); diff --git a/src/@shared/src/database/init.dbml b/src/@shared/src/database/init.dbml index 1a663de..133e482 100644 --- a/src/@shared/src/database/init.dbml +++ b/src/@shared/src/database/init.dbml @@ -18,7 +18,7 @@ Project Transcendance { Table user { id text [PK, not null] login text [unique] - name text [not null] + name text [not null, unique] password text [null, Note: "If password is NULL, this means that the user is created through OAUTH2 or guest login"] otp text [null, Note: "If otp is NULL, then the user didn't configure 2FA"] guest integer [not null, default: 0] diff --git a/src/@shared/src/database/init.sql b/src/@shared/src/database/init.sql index b77edfe..bf363fb 100644 --- a/src/@shared/src/database/init.sql +++ b/src/@shared/src/database/init.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS user ( id TEXT PRIMARY KEY NOT NULL, login TEXT UNIQUE, - name TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, password TEXT, otp TEXT, guest INTEGER NOT NULL DEFAULT 0, diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index a931aa8..fbdaba1 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -3,6 +3,7 @@ import { Otp } from '@shared/auth'; import { isNullish } from '@shared/utils'; import * as bcrypt from 'bcrypt'; import { UUID, newUUID } from '@shared/utils/uuid'; +import { SqliteError } from 'better-sqlite3'; // never use this directly @@ -20,6 +21,10 @@ export interface IUserDb extends Database { getAllUserFromProvider(provider: string): User[] | undefined, getAllUsers(this: IUserDb): User[] | undefined, + + updateDisplayName(id: UserId, new_name: string): boolean, + + getUserFromDisplayName(name: string): User | undefined, }; export const UserImpl: Omit = { @@ -159,6 +164,24 @@ export const UserImpl: Omit = { const req = this.prepare('SELECT * FROM user WHERE oauth2 = @oauth2').get({ oauth2: `${provider}:${unique}` }) as Partial | undefined; return userFromRow(req); }, + + updateDisplayName(this: IUserDb, id: UserId, new_name: string): boolean { + try { + this.prepare('UPDATE OR FAIL user SET name = @new_name WHERE id = @id').run({ id, new_name }); + return true; + } + catch (e) { + if (e instanceof SqliteError) { + if (e.code === 'SQLITE_CONSTRAINT_UNIQUE') return false; + } + throw e; + } + }, + + getUserFromDisplayName(this: IUserDb, name: string) { + const res = this.prepare('SELECT * FROM user WHERE name = @name LIMIT 1').get({ name }) as User | undefined; + return userFromRow(res); + }, }; export type UserId = UUID; @@ -170,7 +193,7 @@ export type User = { readonly password?: string; readonly otp?: string; readonly guest: boolean; - // will be split/merged from the `provider` column + // will be split/merged from the `oauth2` column readonly provider_name?: string; readonly provider_unique?: string; }; @@ -207,7 +230,7 @@ async function hashPassword( * * @returns The user if it exists, undefined otherwise */ -export function userFromRow(row?: Partial & { provider?: string }>): User | undefined { +export function userFromRow(row?: Partial & { oauth2?: string }>): User | undefined { if (isNullish(row)) return undefined; if (isNullish(row.id)) return undefined; if (isNullish(row.name)) return undefined; @@ -216,9 +239,9 @@ export function userFromRow(row?: Partial; +export const GuestLoginReq = Type.Object({ + name: Type.Optional(Type.String()), +}); + +export type GuestLoginReq = Static; + const getRandomFromList = (list: string[]): string => { return list[Math.floor(Math.random() * list.length)]; }; +const USERNAME_CHECK: RegExp = /^[a-zA-Z_0-9]+$/; + const route: FastifyPluginAsync = async (fastify, _opts): Promise => { void _opts; - fastify.post<{ Body: null, Reply: GuestLoginRes }>( + fastify.post<{ Body: GuestLoginReq; Reply: GuestLoginRes }>( '/api/auth/guest', - { schema: { response: GuestLoginRes, operationId: 'guestLogin' } }, + { + schema: { + body: GuestLoginReq, + response: GuestLoginRes, + operationId: 'guestLogin', + }, + }, async function(req, res) { void req; void res; try { - console.log('DEBUG ----- guest login backend'); - const adjective = getRandomFromList(fastify.words.adjectives); - const noun = getRandomFromList(fastify.words.nouns); + let user_name: string | undefined = req.body?.name; + if (isNullish(user_name)) { + const adjective = getRandomFromList( + fastify.words.adjectives, + ); + const noun = getRandomFromList(fastify.words.nouns); + user_name = `${adjective}${noun}`; + } + else { + if (user_name.length < 4 || user_name.length > 26) { + return res.makeResponse( + 400, + 'failed', + 'guestLogin.failed.invalid', + ); + } + if (!USERNAME_CHECK.test(user_name)) { + return res.makeResponse( + 400, + 'failed', + 'guestLogin.failed.invalid', + ); + } + user_name = `g_${user_name}`; + } - const user = await this.db.createGuestUser(`${adjective} ${noun}`); + const orig = user_name; + let i = 0; + while ( + this.db.getUserFromDisplayName(user_name) !== undefined && + i++ < 5 + ) { + user_name = `${orig}${Date.now() % 1000}`; + } + if (this.db.getUserFromDisplayName(user_name) !== undefined) { + user_name = `${orig}${Date.now()}`; + } + + const user = await this.db.createGuestUser(user_name); if (isNullish(user)) { - return res.makeResponse(500, 'failed', 'guestLogin.failed.generic.unknown'); + return res.makeResponse( + 500, + 'failed', + 'guestLogin.failed.generic.unknown', + ); } return res.makeResponse(200, 'success', 'guestLogin.success', { token: this.signJwt('auth', user.id.toString()), @@ -41,7 +97,11 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { } catch (e: unknown) { fastify.log.error(e); - return res.makeResponse(500, 'failed', 'guestLogin.failed.generic.error'); + return res.makeResponse( + 500, + 'failed', + 'guestLogin.failed.generic.error', + ); } }, ); diff --git a/src/auth/src/routes/oauth2/callback.ts b/src/auth/src/routes/oauth2/callback.ts index 10bc72a..36ec4a3 100644 --- a/src/auth/src/routes/oauth2/callback.ts +++ b/src/auth/src/routes/oauth2/callback.ts @@ -30,9 +30,23 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { const result = await creq.getCode(); const userinfo = await provider.getUserInfo(result); + + let u = this.db.getOauth2User(provider.display_name, userinfo.unique_id); if (isNullish(u)) { - u = await this.db.createOauth2User(userinfo.name, provider.display_name, userinfo.unique_id); + let user_name = userinfo.name; + const orig = user_name; + let i = 0; + while ( + this.db.getUserFromDisplayName(user_name) !== undefined && + i++ < 100 + ) { + user_name = `${orig}${Date.now() % 1000}`; + } + if (this.db.getUserFromDisplayName(user_name) !== undefined) { + user_name = `${orig}${Date.now()}`; + } + u = await this.db.createOauth2User(user_name, provider.display_name, userinfo.unique_id); } if (isNullish(u)) { return res.code(500).send('failed to fetch or create user...'); diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index f8b304a..fe09163 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -47,7 +47,19 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { // password is good too ! if (this.db.getUserFromLoginName(name) !== undefined) { return res.makeResponse(400, 'failed', 'signin.failed.username.existing'); } - const u = await this.db.createUser(name, name, password); + let user_name = name; + const orig = user_name; + let i = 0; + while ( + this.db.getUserFromDisplayName(user_name) !== undefined && + i++ < 100 + ) { + user_name = `${orig}${Date.now() % 1000}`; + } + if (this.db.getUserFromDisplayName(user_name) !== undefined) { + user_name = `${orig}${Date.now()}`; + } + const u = await this.db.createUser(name, user_name, password); if (isNullish(u)) { return res.makeResponse(500, 'failed', 'signin.failed.generic'); } // every check has been passed, they are now logged in, using this token to say who they are... diff --git a/src/openapi.json b/src/openapi.json index 4ab32ff..d9b52b6 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -352,6 +352,20 @@ "/api/auth/guest": { "post": { "operationId": "guestLogin", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + }, "responses": { "200": { "description": "Default Response", @@ -392,6 +406,32 @@ } } }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failed" + ] + }, + "msg": { + "enum": [ + "guestLogin.failed.invalid" + ] + } + } + } + } + } + }, "500": { "description": "Default Response", "content": { @@ -1057,6 +1097,117 @@ ] } }, + "/api/user/changeDisplayName": { + "put": { + "operationId": "changeDisplayName", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "New Display Name" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "changeDisplayName.success" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "changeDisplayName.alreadyExist", + "changeDisplayName.invalid" + ] + } + } + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "notLoggedIn" + ] + }, + "msg": { + "enum": [ + "auth.noCookie", + "auth.invalidKind", + "auth.noUser", + "auth.invalid" + ] + } + } + } + } + } + } + }, + "tags": [ + "openapi_other" + ] + } + }, "/api/user/info/{user}": { "get": { "operationId": "getUser", diff --git a/src/user/openapi.json b/src/user/openapi.json index 87744dc..8ad61f7 100644 --- a/src/user/openapi.json +++ b/src/user/openapi.json @@ -8,6 +8,114 @@ "schemas": {} }, "paths": { + "/api/user/changeDisplayName": { + "put": { + "operationId": "changeDisplayName", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "New Display Name" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "changeDisplayName.success" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failure" + ] + }, + "msg": { + "enum": [ + "changeDisplayName.alreadyExist", + "changeDisplayName.invalid" + ] + } + } + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "notLoggedIn" + ] + }, + "msg": { + "enum": [ + "auth.noCookie", + "auth.invalidKind", + "auth.noUser", + "auth.invalid" + ] + } + } + } + } + } + } + } + } + }, "/api/user/info/{user}": { "get": { "operationId": "getUser", diff --git a/src/user/src/routes/changeDisplayName.ts b/src/user/src/routes/changeDisplayName.ts new file mode 100644 index 0000000..f089c59 --- /dev/null +++ b/src/user/src/routes/changeDisplayName.ts @@ -0,0 +1,44 @@ +import { FastifyPluginAsync } from 'fastify'; + +import { Static, Type } from 'typebox'; +import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils'; + + +export const ChangeDisplayNameRes = { + '200': typeResponse('success', 'changeDisplayName.success'), + '400': typeResponse('failure', ['changeDisplayName.alreadyExist', 'changeDisplayName.invalid']), +}; + +export type ChangeDisplayNameRes = MakeStaticResponse; + +export const ChangeDisplayNameReq = Type.Object({ name: Type.String({ description: 'New Display Name' }) }); +type ChangeDisplayNameReq = Static; + +const USERNAME_CHECK: RegExp = /^[a-zA-Z_0-9]+$/; +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; + fastify.put<{ Body: ChangeDisplayNameReq }>( + '/api/user/changeDisplayName', + { schema: { body: ChangeDisplayNameReq, response: ChangeDisplayNameRes, operationId: 'changeDisplayName' }, config: { requireAuth: true } }, + async function(req, res) { + if (isNullish(req.authUser)) return; + if (isNullish(req.body.name)) { + return res.makeResponse(400, 'failure', 'changeDisplayName.invalid'); + } + if (req.body.name.length < 4 || req.body.name.length > 32) { + return res.makeResponse(400, 'failure', 'changeDisplayName.invalid'); + } + if (!USERNAME_CHECK.test(req.body.name)) { + return res.makeResponse(400, 'failure', 'changeDisplayName.invalid'); + } + if (this.db.updateDisplayName(req.authUser.id, req.body.name)) { + return res.makeResponse(200, 'success', 'changeDisplayName.success'); + } + else { + return res.makeResponse(400, 'failure', 'changeDisplayName.alreadyExist'); + } + }, + ); +}; + +export default route; diff --git a/src/user/src/routes/info.ts b/src/user/src/routes/info.ts index 869835e..784ea23 100644 --- a/src/user/src/routes/info.ts +++ b/src/user/src/routes/info.ts @@ -49,7 +49,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { if (isNullish(user)) { return res.makeResponse(404, 'failure', 'userinfo.failure.unknownUser'); } - + console.log(user); const payload = { name: user.name, From 23baa4af5688e02244355008822431e1a68be9dd Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Wed, 10 Dec 2025 16:50:31 +0100 Subject: [PATCH 55/66] almost done --- frontend/src/pages/profile/profile.html | 53 ++- frontend/src/pages/profile/profile.ts | 428 ++++++++++++------------ 2 files changed, 232 insertions(+), 249 deletions(-) diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html index 6752e23..76c7565 100644 --- a/frontend/src/pages/profile/profile.html +++ b/frontend/src/pages/profile/profile.html @@ -1,18 +1,14 @@

Edit Profile

- -
- - + + Unknown
- - @@ -39,54 +36,42 @@ - +
- - -
+ - -
+ -
diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts index 39171ce..4b91b84 100644 --- a/frontend/src/pages/profile/profile.ts +++ b/frontend/src/pages/profile/profile.ts @@ -6,15 +6,7 @@ import { isNullish } from "@app/utils"; import client from "@app/api"; import QRCode from "qrcode"; -type OAuthQRCodeOptions = { - label?: string; // e.g. your-app:user@example.com - issuer?: string; // e.g. "YourApp" - algorithm?: "SHA1" | "SHA256" | "SHA512"; - digits?: number; - period?: number; -}; - -/** +/* * Renders an OAuth2-compatible TOTP QR code into a canvas. * * @param canvas HTMLCanvasElement to draw into @@ -38,219 +30,225 @@ export async function renderOAuth2QRCode( }); canvas.style.width = ""; canvas.style.height = ""; - function removeBgColor(...elem: HTMLElement[]) { - for (let e of elem) { - for (let c of e.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - e.classList.remove(c); - } +} +function removeBgColor(...elem: HTMLElement[]) { + for (let e of elem) { + for (let c of e.classList.values()) { + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) + e.classList.remove(c); } } +} - async function route(url: string, _args: { [k: string]: string }) { - setTitle("Edit Profile"); - return { - html: page, - postInsert: async (app: HTMLElement | undefined) => { - const user = await updateUser(); - if (isNullish(user)) return showError("No User"); - if (isNullish(app)) return showError("Failed to render"); - let totpState = await (async () => { - let res = await client.statusOtp(); - if (res.kind === "success") - return { - enabled: - (res.msg as string) === "statusOtp.success.enabled", - secret: - (res.msg as string) === "statusOtp.success.enabled" - ? res.payload.secret - : null, - }; - else { - showError("Failed to get OTP status"); - return { - enabled: false, - secret: null, - }; - } - })(); - // ---- Simulated State ---- - let totpEnabled = totpState.enabled; - let totpSecret = totpState.secret; // would come from backend - - let guestBox = app.querySelector("#isGuestBox")!; - let displayNameWrapper = app.querySelector( - "#displayNameWrapper", - )!; - let displayNameBox = - app.querySelector("#displayNameBox")!; - let displayNameButton = - app.querySelector("#displayNameButton")!; - let loginNameWrapper = - app.querySelector("#loginNameWrapper")!; - let loginNameBox = - app.querySelector("#loginNameBox")!; - let passwordWrapper = - app.querySelector("#passwordWrapper")!; - let passwordBox = - app.querySelector("#passwordBox")!; - let passwordButton = - app.querySelector("#passwordButton")!; - - let providerWrapper = - app.querySelector("#providerWrapper")!; - let providerNameBox = - app.querySelector("#providerNameBox")!; - let providerUserBox = - app.querySelector("#providerUserBox")!; - - let accountTypeBox = - app.querySelector("#accountType")!; - displayNameBox.value = user.name; - - guestBox.hidden = !user.guest; - - // ---- DOM Elements ---- - const totpStatusText = app.querySelector("#totpStatusText")!; - const enableBtn = - app.querySelector("#enableTotp")!; - const disableBtn = - app.querySelector("#disableTotp")!; - const showSecretBtn = - app.querySelector("#showSecret")!; - const secretBox = app.querySelector("#totpSecretBox")!; - const secretText = - app.querySelector("#totpSecretText")!; - const secretCanvas = - app.querySelector("#totpSecretCanvas")!; - - if (user.guest) { - for (let c of passwordButton.classList.values()) { - if (c.startsWith("bg-") || c.startsWith("hover:bg-")) - passwordButton.classList.remove(c); - } - let totpWrapper = app.querySelector("#totpWrapper")!; - - if (user.guest) { - removeBgColor( - passwordButton, - displayNameButton, - enableBtn, - disableBtn, - showSecretBtn, - ); - - passwordButton.classList.add( - "bg-gray-700", - "hover:bg-gray-700", - ); - - passwordBox.disabled = true; - passwordBox.classList.add("color-white"); - - displayNameButton.disabled = true; - displayNameButton.classList.add("bg-gray-700", "color-white"); - - displayNameBox.disabled = true; - displayNameBox.classList.add("color-white"); - enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - - enableBtn.disabled = true; - disableBtn.disabled = true; - showSecretBtn.disabled = true; - - accountTypeBox.innerText = "Guest"; - } else if (!isNullish(user.selfInfo?.loginName)) { - loginNameWrapper.hidden = false; - loginNameBox.innerText = user.selfInfo.loginName; - - accountTypeBox.innerText = "Normal"; - } else if ( - !isNullish(user.selfInfo?.providerId) && - !isNullish(user.selfInfo?.providerUser) - ) { - providerWrapper.hidden = false; - providerNameBox.innerText = user.selfInfo.providerId; - providerUserBox.innerText = user.selfInfo.providerUser; - - enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); - - enableBtn.disabled = true; - disableBtn.disabled = true; - showSecretBtn.disabled = true; - - removeBgColor(enableBtn, disableBtn, showSecretBtn); - passwordWrapper.hidden = true; - totpWrapper.hidden = true; - - accountTypeBox.innerText = "Provider"; - } - - // ---- Update UI ---- - function refreshTotpUI() { - if (totpEnabled) { - totpStatusText.textContent = "Status: Enabled"; - - enableBtn.classList.add("hidden"); - disableBtn.classList.remove("hidden"); - showSecretBtn.classList.remove("hidden"); - } else { - totpStatusText.textContent = "Status: Disabled"; - - enableBtn.classList.remove("hidden"); - disableBtn.classList.add("hidden"); - showSecretBtn.classList.add("hidden"); - secretBox.classList.add("hidden"); - } - } - - // ---- Button Events ---- - enableBtn.onclick = async () => { - let res = await client.enableOtp(); - if (res.kind === "success") { - navigateTo(url); - } else { - showError(`failed to activate OTP: ${res.msg}`); - } +async function route(url: string, _args: { [k: string]: string }) { + setTitle("Edit Profile"); + return { + html: page, + postInsert: async (app: HTMLElement | undefined) => { + const user = await updateUser(); + if (isNullish(user)) return showError("No User"); + if (isNullish(app)) return showError("Failed to render"); + let totpState = await (async () => { + let res = await client.statusOtp(); + if (res.kind === "success") + return { + enabled: + (res.msg as string) === "statusOtp.success.enabled", + secret: + (res.msg as string) === "statusOtp.success.enabled" + ? res.payload.secret + : null, }; - - disableBtn.onclick = async () => { - let res = await client.disableOtp(); - if (res.kind === "success") { - navigateTo(url); - } else { - showError(`failed to deactivate OTP: ${res.msg}`); - } + else { + showError("Failed to get OTP status"); + return { + enabled: false, + secret: null, }; + } + })(); + // ---- Simulated State ---- + let totpEnabled = totpState.enabled; + let totpSecret = totpState.secret; // would come from backend - showSecretBtn.onclick = () => { - if (!isNullish(totpSecret)) { - secretText.textContent = totpSecret; - renderOAuth2QRCode(secretCanvas, totpSecret); - } - secretBox.classList.toggle("hidden"); - }; + let guestBox = app.querySelector("#isGuestBox")!; + let displayNameWrapper = app.querySelector( + "#displayNameWrapper", + )!; + let displayNameBox = + app.querySelector("#displayNameBox")!; + let displayNameButton = + app.querySelector("#displayNameButton")!; + let loginNameWrapper = + app.querySelector("#loginNameWrapper")!; + let loginNameBox = + app.querySelector("#loginNameBox")!; + let passwordWrapper = + app.querySelector("#passwordWrapper")!; + let passwordBox = + app.querySelector("#passwordBox")!; + let passwordButton = + app.querySelector("#passwordButton")!; - displayNameButton.onclick = async () => { - let req = await client.changeDisplayName({ - changeDisplayNameRequest: { name: displayNameBox.value }, - }); - if (req.kind === "success") { - showSuccess("Successfully changed display name"); - handleRoute(); - } else { - showError(`Failed to update: ${req.msg}`); - } - }; + let providerWrapper = + app.querySelector("#providerWrapper")!; + let providerNameBox = + app.querySelector("#providerNameBox")!; + let providerUserBox = + app.querySelector("#providerUserBox")!; - // Initialize UI state - refreshTotpUI(); - }, + let accountTypeBox = + app.querySelector("#accountType")!; + displayNameBox.value = user.name; + + guestBox.hidden = !user.guest; + + // ---- DOM Elements ---- + const totpStatusText = app.querySelector("#totpStatusText")!; + const enableBtn = + app.querySelector("#enableTotp")!; + const disableBtn = + app.querySelector("#disableTotp")!; + const showSecretBtn = + app.querySelector("#showSecret")!; + const secretBox = app.querySelector("#totpSecretBox")!; + const secretText = + app.querySelector("#totpSecretText")!; + const secretCanvas = + app.querySelector("#totpSecretCanvas")!; + let totpWrapper = + app.querySelector("#totpWrapper")!; + + if (user.guest) { + for (let c of passwordButton.classList.values()) { + if (c.startsWith("bg-") || c.startsWith("hover:bg-")) + passwordButton.classList.remove(c); + } + } + if (user.guest) { + removeBgColor( + passwordButton, + displayNameButton, + enableBtn, + disableBtn, + showSecretBtn, + ); + + passwordButton.classList.add( + "bg-gray-700", + "hover:bg-gray-700", + ); + + passwordBox.disabled = true; + passwordBox.classList.add("color-white"); + + displayNameButton.disabled = true; + displayNameButton.classList.add("bg-gray-700", "color-white"); + + displayNameBox.disabled = true; + displayNameBox.classList.add("color-white"); + enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + + enableBtn.disabled = true; + disableBtn.disabled = true; + showSecretBtn.disabled = true; + + accountTypeBox.innerText = "Guest"; + } else if (!isNullish(user.selfInfo?.loginName)) { + loginNameWrapper.hidden = false; + loginNameBox.innerText = user.selfInfo.loginName; + totpWrapper.hidden =false; + passwordWrapper.hidden = false; + + accountTypeBox.innerText = "Normal"; + } else if ( + !isNullish(user.selfInfo?.providerId) && + !isNullish(user.selfInfo?.providerUser) + ) { + providerWrapper.hidden = false; + providerNameBox.innerText = user.selfInfo.providerId; + providerUserBox.innerText = user.selfInfo.providerUser; + + enableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + disableBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + showSecretBtn.classList.add("bg-gray-700", "hover:bg-gray-700"); + + enableBtn.disabled = true; + disableBtn.disabled = true; + showSecretBtn.disabled = true; + + removeBgColor(enableBtn, disableBtn, showSecretBtn); + passwordWrapper.hidden = true; + totpWrapper.hidden = true; + + accountTypeBox.innerText = "Provider"; + } + + // ---- Update UI ---- + function refreshTotpUI() { + if (totpEnabled) { + totpStatusText.textContent = "Status: Enabled"; + + enableBtn.classList.add("hidden"); + disableBtn.classList.remove("hidden"); + showSecretBtn.classList.remove("hidden"); + } else { + totpStatusText.textContent = "Status: Disabled"; + + enableBtn.classList.remove("hidden"); + disableBtn.classList.add("hidden"); + showSecretBtn.classList.add("hidden"); + secretBox.classList.add("hidden"); + } + } + + // ---- Button Events ---- + enableBtn.onclick = async () => { + let res = await client.enableOtp(); + if (res.kind === "success") { + navigateTo(url); + } else { + showError(`failed to activate OTP: ${res.msg}`); + } }; - } - addRoute("/profile", route); + disableBtn.onclick = async () => { + let res = await client.disableOtp(); + if (res.kind === "success") { + navigateTo(url); + } else { + showError(`failed to deactivate OTP: ${res.msg}`); + } + }; + + showSecretBtn.onclick = () => { + if (!isNullish(totpSecret)) { + secretText.textContent = totpSecret; + renderOAuth2QRCode(secretCanvas, totpSecret); + } + secretBox.classList.toggle("hidden"); + }; + + displayNameButton.onclick = async () => { + let req = await client.changeDisplayName({ + changeDisplayNameRequest: { + name: displayNameBox.value, + }, + }); + if (req.kind === "success") { + showSuccess("Successfully changed display name"); + handleRoute(); + } else { + showError(`Failed to update: ${req.msg}`); + } + }; + + // Initialize UI state + refreshTotpUI(); + }, + }; +} + +addRoute("/profile", route); From a8fa9f984dabbcdd36cdb63dd5a24c251008c030 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Wed, 10 Dec 2025 17:06:53 +0100 Subject: [PATCH 56/66] removed icon server and finished profile page --- docker-compose.yml | 27 ---- .../api/generated/.openapi-generator/FILES | 6 +- .../src/api/generated/apis/OpenapiOtherApi.ts | 94 ++++++++++-- .../models/ChangePassword200Response.ts | 93 ++++++++++++ .../models/ChangePassword400Response.ts | 95 ++++++++++++ .../models/ChangePassword401Response.ts | 96 ++++++++++++ .../models/ChangePassword500Response.ts | 93 ++++++++++++ .../generated/models/ChangePasswordRequest.ts | 66 +++++++++ .../generated/models/EnableOtp401Response.ts | 14 +- .../generated/models/StatusOtp401Response.ts | 12 +- frontend/src/api/generated/models/index.ts | 6 +- frontend/src/pages/profile/profile.ts | 17 ++- src/auth/openapi.json | 134 +++++++++++++++++ src/auth/src/routes/changePassword.ts | 44 ++++++ src/icons/.dockerignore | 2 - src/icons/entrypoint.sh | 10 -- src/icons/extra/.gitkeep | 0 src/icons/package.json | 37 ----- src/icons/src/app.ts | 54 ------- src/icons/src/plugins/README.md | 16 -- src/icons/src/plugins/sensible.ts | 11 -- src/icons/src/routes/set.ts | 51 ------- src/icons/src/run.ts | 35 ----- src/icons/tsconfig.json | 5 - src/icons/vite.config.js | 51 ------- src/openapi.json | 137 ++++++++++++++++++ 26 files changed, 881 insertions(+), 325 deletions(-) create mode 100644 frontend/src/api/generated/models/ChangePassword200Response.ts create mode 100644 frontend/src/api/generated/models/ChangePassword400Response.ts create mode 100644 frontend/src/api/generated/models/ChangePassword401Response.ts create mode 100644 frontend/src/api/generated/models/ChangePassword500Response.ts create mode 100644 frontend/src/api/generated/models/ChangePasswordRequest.ts create mode 100644 src/auth/src/routes/changePassword.ts delete mode 100644 src/icons/.dockerignore delete mode 100644 src/icons/entrypoint.sh delete mode 100644 src/icons/extra/.gitkeep delete mode 100644 src/icons/package.json delete mode 100644 src/icons/src/app.ts delete mode 100644 src/icons/src/plugins/README.md delete mode 100644 src/icons/src/plugins/sensible.ts delete mode 100644 src/icons/src/routes/set.ts delete mode 100644 src/icons/src/run.ts delete mode 100644 src/icons/tsconfig.json delete mode 100644 src/icons/vite.config.js diff --git a/docker-compose.yml b/docker-compose.yml index 45f7af3..33f54fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,33 +48,6 @@ services: gelf-address: "udp://127.0.0.1:12201" tag: "{{.Name}}" - ############### - # ICONS # - ############### - icons: - build: - context: ./src/ - args: - - SERVICE=icons - #- EXTRA_FILES=icons/extra - container_name: icons - restart: always - networks: - - transcendance-network - volumes: - - images-volume:/volumes/store - - sqlite-volume:/volumes/database - environment: - - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA - - USER_ICONS_STORE=/volumes/store - - DATABASE_DIR=/volumes/database - logging: - driver: gelf - options: - gelf-address: "udp://127.0.0.1:12201" - tag: "{{.Name}}" - - ############### # AUTH # ############### diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index ed23b0c..dbddaec 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -4,11 +4,15 @@ index.ts models/ChangeDisplayName200Response.ts models/ChangeDisplayName400Response.ts models/ChangeDisplayNameRequest.ts +models/ChangePassword200Response.ts +models/ChangePassword400Response.ts +models/ChangePassword401Response.ts +models/ChangePassword500Response.ts +models/ChangePasswordRequest.ts models/ChatTest200Response.ts models/ChatTest200ResponsePayload.ts models/DisableOtp200Response.ts models/DisableOtp400Response.ts -models/DisableOtp401Response.ts models/DisableOtp500Response.ts models/EnableOtp200Response.ts models/EnableOtp200ResponsePayload.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index 9609421..444695b 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -18,10 +18,14 @@ import type { ChangeDisplayName200Response, ChangeDisplayName400Response, ChangeDisplayNameRequest, + ChangePassword200Response, + ChangePassword400Response, + ChangePassword401Response, + ChangePassword500Response, + ChangePasswordRequest, ChatTest200Response, DisableOtp200Response, DisableOtp400Response, - DisableOtp401Response, DisableOtp500Response, EnableOtp200Response, EnableOtp400Response, @@ -60,14 +64,22 @@ import { ChangeDisplayName400ResponseToJSON, ChangeDisplayNameRequestFromJSON, ChangeDisplayNameRequestToJSON, + ChangePassword200ResponseFromJSON, + ChangePassword200ResponseToJSON, + ChangePassword400ResponseFromJSON, + ChangePassword400ResponseToJSON, + ChangePassword401ResponseFromJSON, + ChangePassword401ResponseToJSON, + ChangePassword500ResponseFromJSON, + ChangePassword500ResponseToJSON, + ChangePasswordRequestFromJSON, + ChangePasswordRequestToJSON, ChatTest200ResponseFromJSON, ChatTest200ResponseToJSON, DisableOtp200ResponseFromJSON, DisableOtp200ResponseToJSON, DisableOtp400ResponseFromJSON, DisableOtp400ResponseToJSON, - DisableOtp401ResponseFromJSON, - DisableOtp401ResponseToJSON, DisableOtp500ResponseFromJSON, DisableOtp500ResponseToJSON, EnableOtp200ResponseFromJSON, @@ -134,6 +146,10 @@ export interface ChangeDisplayNameOperationRequest { changeDisplayNameRequest: ChangeDisplayNameRequest; } +export interface ChangePasswordOperationRequest { + changePasswordRequest: ChangePasswordRequest; +} + export interface GetUserRequest { user: GetUserUserParameter; } @@ -161,7 +177,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async changeDisplayNameRaw(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async changeDisplayNameRaw(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { if (requestParameters['changeDisplayNameRequest'] == null) { throw new runtime.RequiredError( 'changeDisplayNameRequest', @@ -200,7 +216,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { } if (response.status === 401) { // Object response for status 401 - return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue)); + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword401ResponseFromJSON(jsonValue)); } // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes @@ -210,11 +226,71 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async changeDisplayName(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async changeDisplayName(requestParameters: ChangeDisplayNameOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.changeDisplayNameRaw(requestParameters, initOverrides); return await response.value(); } + /** + */ + async changePasswordRaw(requestParameters: ChangePasswordOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['changePasswordRequest'] == null) { + throw new runtime.RequiredError( + 'changePasswordRequest', + 'Required parameter "changePasswordRequest" was null or undefined when calling changePassword().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + + let urlPath = `/api/auth/changePassword`; + + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: ChangePasswordRequestToJSON(requestParameters['changePasswordRequest']), + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword200ResponseFromJSON(jsonValue)); + } + if (response.status === 400) { + // Object response for status 400 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword400ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword401ResponseFromJSON(jsonValue)); + } + if (response.status === 500) { + // Object response for status 500 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword500ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401, 500`); + } + + /** + */ + async changePassword(requestParameters: ChangePasswordOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.changePasswordRaw(requestParameters, initOverrides); + return await response.value(); + } + /** */ async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -259,7 +335,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -288,7 +364,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { } if (response.status === 401) { // Object response for status 401 - return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue)); + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword401ResponseFromJSON(jsonValue)); } if (response.status === 500) { // Object response for status 500 @@ -302,7 +378,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.disableOtpRaw(initOverrides); return await response.value(); } diff --git a/frontend/src/api/generated/models/ChangePassword200Response.ts b/frontend/src/api/generated/models/ChangePassword200Response.ts new file mode 100644 index 0000000..0df6906 --- /dev/null +++ b/frontend/src/api/generated/models/ChangePassword200Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangePassword200Response + */ +export interface ChangePassword200Response { + /** + * + * @type {string} + * @memberof ChangePassword200Response + */ + kind: ChangePassword200ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangePassword200Response + */ + msg: ChangePassword200ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangePassword200ResponseKindEnum = { + Success: 'success' +} as const; +export type ChangePassword200ResponseKindEnum = typeof ChangePassword200ResponseKindEnum[keyof typeof ChangePassword200ResponseKindEnum]; + +/** + * @export + */ +export const ChangePassword200ResponseMsgEnum = { + ChangePasswordSuccess: 'changePassword.success' +} as const; +export type ChangePassword200ResponseMsgEnum = typeof ChangePassword200ResponseMsgEnum[keyof typeof ChangePassword200ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangePassword200Response interface. + */ +export function instanceOfChangePassword200Response(value: object): value is ChangePassword200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangePassword200ResponseFromJSON(json: any): ChangePassword200Response { + return ChangePassword200ResponseFromJSONTyped(json, false); +} + +export function ChangePassword200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangePassword200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangePassword200ResponseToJSON(json: any): ChangePassword200Response { + return ChangePassword200ResponseToJSONTyped(json, false); +} + +export function ChangePassword200ResponseToJSONTyped(value?: ChangePassword200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangePassword400Response.ts b/frontend/src/api/generated/models/ChangePassword400Response.ts new file mode 100644 index 0000000..1606223 --- /dev/null +++ b/frontend/src/api/generated/models/ChangePassword400Response.ts @@ -0,0 +1,95 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangePassword400Response + */ +export interface ChangePassword400Response { + /** + * + * @type {string} + * @memberof ChangePassword400Response + */ + kind: ChangePassword400ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangePassword400Response + */ + msg: ChangePassword400ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangePassword400ResponseKindEnum = { + Failed: 'failed' +} as const; +export type ChangePassword400ResponseKindEnum = typeof ChangePassword400ResponseKindEnum[keyof typeof ChangePassword400ResponseKindEnum]; + +/** + * @export + */ +export const ChangePassword400ResponseMsgEnum = { + ChangePasswordFailedToolong: 'changePassword.failed.toolong', + ChangePasswordFailedTooshort: 'changePassword.failed.tooshort', + ChangePasswordFailedInvalid: 'changePassword.failed.invalid' +} as const; +export type ChangePassword400ResponseMsgEnum = typeof ChangePassword400ResponseMsgEnum[keyof typeof ChangePassword400ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangePassword400Response interface. + */ +export function instanceOfChangePassword400Response(value: object): value is ChangePassword400Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangePassword400ResponseFromJSON(json: any): ChangePassword400Response { + return ChangePassword400ResponseFromJSONTyped(json, false); +} + +export function ChangePassword400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangePassword400Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangePassword400ResponseToJSON(json: any): ChangePassword400Response { + return ChangePassword400ResponseToJSONTyped(json, false); +} + +export function ChangePassword400ResponseToJSONTyped(value?: ChangePassword400Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangePassword401Response.ts b/frontend/src/api/generated/models/ChangePassword401Response.ts new file mode 100644 index 0000000..7e460c7 --- /dev/null +++ b/frontend/src/api/generated/models/ChangePassword401Response.ts @@ -0,0 +1,96 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangePassword401Response + */ +export interface ChangePassword401Response { + /** + * + * @type {string} + * @memberof ChangePassword401Response + */ + kind: ChangePassword401ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangePassword401Response + */ + msg: ChangePassword401ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangePassword401ResponseKindEnum = { + NotLoggedIn: 'notLoggedIn' +} as const; +export type ChangePassword401ResponseKindEnum = typeof ChangePassword401ResponseKindEnum[keyof typeof ChangePassword401ResponseKindEnum]; + +/** + * @export + */ +export const ChangePassword401ResponseMsgEnum = { + AuthNoCookie: 'auth.noCookie', + AuthInvalidKind: 'auth.invalidKind', + AuthNoUser: 'auth.noUser', + AuthInvalid: 'auth.invalid' +} as const; +export type ChangePassword401ResponseMsgEnum = typeof ChangePassword401ResponseMsgEnum[keyof typeof ChangePassword401ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangePassword401Response interface. + */ +export function instanceOfChangePassword401Response(value: object): value is ChangePassword401Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangePassword401ResponseFromJSON(json: any): ChangePassword401Response { + return ChangePassword401ResponseFromJSONTyped(json, false); +} + +export function ChangePassword401ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangePassword401Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangePassword401ResponseToJSON(json: any): ChangePassword401Response { + return ChangePassword401ResponseToJSONTyped(json, false); +} + +export function ChangePassword401ResponseToJSONTyped(value?: ChangePassword401Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangePassword500Response.ts b/frontend/src/api/generated/models/ChangePassword500Response.ts new file mode 100644 index 0000000..2d345f7 --- /dev/null +++ b/frontend/src/api/generated/models/ChangePassword500Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangePassword500Response + */ +export interface ChangePassword500Response { + /** + * + * @type {string} + * @memberof ChangePassword500Response + */ + kind: ChangePassword500ResponseKindEnum; + /** + * + * @type {string} + * @memberof ChangePassword500Response + */ + msg: ChangePassword500ResponseMsgEnum; +} + + +/** + * @export + */ +export const ChangePassword500ResponseKindEnum = { + Failed: 'failed' +} as const; +export type ChangePassword500ResponseKindEnum = typeof ChangePassword500ResponseKindEnum[keyof typeof ChangePassword500ResponseKindEnum]; + +/** + * @export + */ +export const ChangePassword500ResponseMsgEnum = { + ChangePasswordFailedGeneric: 'changePassword.failed.generic' +} as const; +export type ChangePassword500ResponseMsgEnum = typeof ChangePassword500ResponseMsgEnum[keyof typeof ChangePassword500ResponseMsgEnum]; + + +/** + * Check if a given object implements the ChangePassword500Response interface. + */ +export function instanceOfChangePassword500Response(value: object): value is ChangePassword500Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ChangePassword500ResponseFromJSON(json: any): ChangePassword500Response { + return ChangePassword500ResponseFromJSONTyped(json, false); +} + +export function ChangePassword500ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangePassword500Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ChangePassword500ResponseToJSON(json: any): ChangePassword500Response { + return ChangePassword500ResponseToJSONTyped(json, false); +} + +export function ChangePassword500ResponseToJSONTyped(value?: ChangePassword500Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ChangePasswordRequest.ts b/frontend/src/api/generated/models/ChangePasswordRequest.ts new file mode 100644 index 0000000..d54b3e1 --- /dev/null +++ b/frontend/src/api/generated/models/ChangePasswordRequest.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ChangePasswordRequest + */ +export interface ChangePasswordRequest { + /** + * + * @type {string} + * @memberof ChangePasswordRequest + */ + newPassword: string; +} + +/** + * Check if a given object implements the ChangePasswordRequest interface. + */ +export function instanceOfChangePasswordRequest(value: object): value is ChangePasswordRequest { + if (!('newPassword' in value) || value['newPassword'] === undefined) return false; + return true; +} + +export function ChangePasswordRequestFromJSON(json: any): ChangePasswordRequest { + return ChangePasswordRequestFromJSONTyped(json, false); +} + +export function ChangePasswordRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangePasswordRequest { + if (json == null) { + return json; + } + return { + + 'newPassword': json['new_password'], + }; +} + +export function ChangePasswordRequestToJSON(json: any): ChangePasswordRequest { + return ChangePasswordRequestToJSONTyped(json, false); +} + +export function ChangePasswordRequestToJSONTyped(value?: ChangePasswordRequest | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'new_password': value['newPassword'], + }; +} + diff --git a/frontend/src/api/generated/models/EnableOtp401Response.ts b/frontend/src/api/generated/models/EnableOtp401Response.ts index 4db1df0..58b0f2f 100644 --- a/frontend/src/api/generated/models/EnableOtp401Response.ts +++ b/frontend/src/api/generated/models/EnableOtp401Response.ts @@ -13,13 +13,6 @@ */ import { mapValues } from '../runtime'; -import type { DisableOtp401Response } from './DisableOtp401Response'; -import { - DisableOtp401ResponseFromJSON, - DisableOtp401ResponseFromJSONTyped, - DisableOtp401ResponseToJSON, - DisableOtp401ResponseToJSONTyped, -} from './DisableOtp401Response'; import type { EnableOtp401ResponseAnyOf } from './EnableOtp401ResponseAnyOf'; import { EnableOtp401ResponseAnyOfFromJSON, @@ -27,6 +20,13 @@ import { EnableOtp401ResponseAnyOfToJSON, EnableOtp401ResponseAnyOfToJSONTyped, } from './EnableOtp401ResponseAnyOf'; +import type { ChangePassword401Response } from './ChangePassword401Response'; +import { + ChangePassword401ResponseFromJSON, + ChangePassword401ResponseFromJSONTyped, + ChangePassword401ResponseToJSON, + ChangePassword401ResponseToJSONTyped, +} from './ChangePassword401Response'; /** * diff --git a/frontend/src/api/generated/models/StatusOtp401Response.ts b/frontend/src/api/generated/models/StatusOtp401Response.ts index 10bde87..b89208a 100644 --- a/frontend/src/api/generated/models/StatusOtp401Response.ts +++ b/frontend/src/api/generated/models/StatusOtp401Response.ts @@ -13,13 +13,13 @@ */ import { mapValues } from '../runtime'; -import type { DisableOtp401Response } from './DisableOtp401Response'; +import type { ChangePassword401Response } from './ChangePassword401Response'; import { - DisableOtp401ResponseFromJSON, - DisableOtp401ResponseFromJSONTyped, - DisableOtp401ResponseToJSON, - DisableOtp401ResponseToJSONTyped, -} from './DisableOtp401Response'; + ChangePassword401ResponseFromJSON, + ChangePassword401ResponseFromJSONTyped, + ChangePassword401ResponseToJSON, + ChangePassword401ResponseToJSONTyped, +} from './ChangePassword401Response'; /** * diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index f00b24e..af6e1a0 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -3,11 +3,15 @@ export * from './ChangeDisplayName200Response'; export * from './ChangeDisplayName400Response'; export * from './ChangeDisplayNameRequest'; +export * from './ChangePassword200Response'; +export * from './ChangePassword400Response'; +export * from './ChangePassword401Response'; +export * from './ChangePassword500Response'; +export * from './ChangePasswordRequest'; export * from './ChatTest200Response'; export * from './ChatTest200ResponsePayload'; export * from './DisableOtp200Response'; export * from './DisableOtp400Response'; -export * from './DisableOtp401Response'; export * from './DisableOtp500Response'; export * from './EnableOtp200Response'; export * from './EnableOtp200ResponsePayload'; diff --git a/frontend/src/pages/profile/profile.ts b/frontend/src/pages/profile/profile.ts index 4b91b84..7241eeb 100644 --- a/frontend/src/pages/profile/profile.ts +++ b/frontend/src/pages/profile/profile.ts @@ -1,4 +1,4 @@ -import { addRoute, handleRoute, navigateTo, setTitle } from "@app/routing"; +import { addRoute, getRoute, handleRoute, navigateTo, setTitle } from "@app/routing"; import { showError, showSuccess } from "@app/toast"; import page from "./profile.html?raw"; import { updateUser } from "@app/auth"; @@ -159,7 +159,7 @@ async function route(url: string, _args: { [k: string]: string }) { } else if (!isNullish(user.selfInfo?.loginName)) { loginNameWrapper.hidden = false; loginNameBox.innerText = user.selfInfo.loginName; - totpWrapper.hidden =false; + totpWrapper.hidden = false; passwordWrapper.hidden = false; accountTypeBox.innerText = "Normal"; @@ -244,6 +244,19 @@ async function route(url: string, _args: { [k: string]: string }) { showError(`Failed to update: ${req.msg}`); } }; + passwordButton.onclick = async () => { + let req = await client.changePassword({ + changePasswordRequest: { + newPassword: passwordBox.value, + }, + }); + if (req.kind === "success") { + showSuccess("Successfully changed password"); + handleRoute(); + } else { + showError(`Failed to update: ${req.msg}`); + } + }; // Initialize UI state refreshTotpUI(); diff --git a/src/auth/openapi.json b/src/auth/openapi.json index 7fd431f..7a08ce2 100644 --- a/src/auth/openapi.json +++ b/src/auth/openapi.json @@ -8,6 +8,140 @@ "schemas": {} }, "paths": { + "/api/auth/changePassword": { + "post": { + "operationId": "changePassword", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_password" + ], + "properties": { + "new_password": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "changePassword.success" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failed" + ] + }, + "msg": { + "enum": [ + "changePassword.failed.toolong", + "changePassword.failed.tooshort", + "changePassword.failed.invalid" + ] + } + } + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "notLoggedIn" + ] + }, + "msg": { + "enum": [ + "auth.noCookie", + "auth.invalidKind", + "auth.noUser", + "auth.invalid" + ] + } + } + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failed" + ] + }, + "msg": { + "enum": [ + "changePassword.failed.generic" + ] + } + } + } + } + } + } + } + } + }, "/api/auth/disableOtp": { "put": { "operationId": "disableOtp", diff --git a/src/auth/src/routes/changePassword.ts b/src/auth/src/routes/changePassword.ts new file mode 100644 index 0000000..7d87d9c --- /dev/null +++ b/src/auth/src/routes/changePassword.ts @@ -0,0 +1,44 @@ +import { FastifyPluginAsync } from 'fastify'; + +import { Static, Type } from 'typebox'; +import { typeResponse, MakeStaticResponse } from '@shared/utils'; + +const ChangePasswordReq = Type.Object({ + new_password: Type.String(), +}); + +type ChangePasswordReq = Static; + +const ChangePasswordRes = { + '500': typeResponse('failed', + 'changePassword.failed.generic'), + '400': typeResponse('failed', [ + 'changePassword.failed.toolong', + 'changePassword.failed.tooshort', + 'changePassword.failed.invalid', + ]), + '200': typeResponse('success', 'changePassword.success'), +}; + +type ChangePasswordRes = MakeStaticResponse; + +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; + fastify.post<{ Body: ChangePasswordReq }>( + '/api/auth/changePassword', + { schema: { body: ChangePasswordReq, response: ChangePasswordRes, operationId: 'changePassword' }, config: { requireAuth: true } }, + async function(req, res) { + const password = req.body.new_password; + + if (password.length < 8) { return res.makeResponse(400, 'failed', 'changePassword.failed.tooshort'); } + if (password.length > 64) { return res.makeResponse(400, 'failed', 'changePassword.failed.toolong'); } + // password is good too ! + + await this.db.setUserPassword(req.authUser!.id, password); + + return res.makeResponse(200, 'success', 'changePassword.success'); + }, + ); +}; + +export default route; diff --git a/src/icons/.dockerignore b/src/icons/.dockerignore deleted file mode 100644 index c925c21..0000000 --- a/src/icons/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist -/node_modules diff --git a/src/icons/entrypoint.sh b/src/icons/entrypoint.sh deleted file mode 100644 index 4a3dac4..0000000 --- a/src/icons/entrypoint.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -set -e -set -x -# do anything here - -cp -r /extra /files - -# run the CMD [ ... ] from the dockerfile -exec "$@" diff --git a/src/icons/extra/.gitkeep b/src/icons/extra/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/icons/package.json b/src/icons/package.json deleted file mode 100644 index bce9ed7..0000000 --- a/src/icons/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "type": "module", - "private": false, - "name": "icons", - "version": "1.0.0", - "description": "This project was bootstrapped with Fastify-CLI.", - "main": "app.ts", - "directories": { - "test": "test" - }, - "scripts": { - "start": "npm run build && node dist/run.js", - "build": "vite build", - "build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@fastify/autoload": "^6.3.1", - "@fastify/formbody": "^8.0.2", - "@fastify/multipart": "^9.3.0", - "@fastify/sensible": "^6.0.4", - "@fastify/static": "^8.3.0", - "fastify": "^5.6.2", - "fastify-cli": "^7.4.1", - "fastify-plugin": "^5.1.0", - "raw-body": "^3.0.2", - "sharp": "^0.34.5" - }, - "devDependencies": { - "@types/node": "^22.19.2", - "rollup-plugin-node-externals": "^8.1.2", - "vite": "^7.2.7", - "vite-tsconfig-paths": "^5.1.4" - } -} diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts deleted file mode 100644 index 7c3b2f0..0000000 --- a/src/icons/src/app.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { FastifyPluginAsync } from 'fastify'; -import fastifyFormBody from '@fastify/formbody'; -import fastifyMultipart from '@fastify/multipart'; -import { mkdir } from 'node:fs/promises'; -import fp from 'fastify-plugin'; -import * as db from '@shared/database'; -import * as utils from '@shared/utils'; -import { authPlugin, jwtPlugin } from '@shared/auth'; - -// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... -const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); -// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... -const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); - - -// When using .decorate you have to specify added properties for Typescript -declare module 'fastify' { - export interface FastifyInstance { - image_store: string; - } -} - -const app: FastifyPluginAsync = async ( - fastify, - _opts, -): Promise => { - void _opts; - // Place here your custom code! - for (const plugin of Object.values(plugins)) { - void fastify.register(plugin as FastifyPluginAsync, {}); - } - for (const route of Object.values(routes)) { - void fastify.register(route as FastifyPluginAsync, {}); - } - - await fastify.register(utils.useMonitoring); - await fastify.register(db.useDatabase as FastifyPluginAsync, {}); - await fastify.register(authPlugin as FastifyPluginAsync, {}); - await fastify.register(jwtPlugin as FastifyPluginAsync, {}); - - void fastify.register(fastifyFormBody, {}); - void fastify.register(fastifyMultipart, {}); - - // The use of fastify-plugin is required to be able - // to export the decorators to the outer scope - void fastify.register(fp(async (fastify2) => { - const image_store = process.env.USER_ICONS_STORE ?? '/tmp/icons'; - fastify2.decorate('image_store', image_store); - await mkdir(fastify2.image_store, { recursive: true }); - })); -}; - -export default app; -export { app }; diff --git a/src/icons/src/plugins/README.md b/src/icons/src/plugins/README.md deleted file mode 100644 index 1e61ee5..0000000 --- a/src/icons/src/plugins/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Plugins Folder - -Plugins define behavior that is common to all the routes in your -application. Authentication, caching, templates, and all the other cross -cutting concerns should be handled by plugins placed in this folder. - -Files in this folder are typically defined through the -[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, -making them non-encapsulated. They can define decorators and set hooks -that will then be used in the rest of your application. - -Check out: - -* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/) -* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/). -* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/). diff --git a/src/icons/src/plugins/sensible.ts b/src/icons/src/plugins/sensible.ts deleted file mode 100644 index 8c2093c..0000000 --- a/src/icons/src/plugins/sensible.ts +++ /dev/null @@ -1,11 +0,0 @@ -import fp from 'fastify-plugin'; -import sensible, { FastifySensibleOptions } from '@fastify/sensible'; - -/** - * This plugins adds some utilities to handle http errors - * - * @see https://github.com/fastify/fastify-sensible - */ -export default fp(async (fastify) => { - fastify.register(sensible); -}); diff --git a/src/icons/src/routes/set.ts b/src/icons/src/routes/set.ts deleted file mode 100644 index 8063719..0000000 --- a/src/icons/src/routes/set.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { FastifyPluginAsync } from 'fastify'; -import { join } from 'node:path'; -import { open } from 'node:fs/promises'; -import sharp from 'sharp'; -import rawBody from 'raw-body'; -import { isNullish } from '@shared/utils'; - -const route: FastifyPluginAsync = async (fastify, _opts): Promise => { - void _opts; - // await fastify.register(authMethod, {}); - // here we register plugins that will be active for the current fastify instance (aka everything in this function) - - // we register a route handler for: `/` - // it sets some configuration options, and set the actual function that will handle the request - - fastify.addContentTypeParser('*', function(request, payload, done) { - done(null); - }); - - fastify.post<{ Params: { userid: string } }>('/:userid', async function(request, reply) { - const buffer = await rawBody(request.raw); - // this is how we get the `:userid` part of things - const userid: string | undefined = (request.params)['userid']; - if (isNullish(userid)) { - return await reply.code(403); - } - const image_store: string = fastify.getDecorator('image_store'); - const image_path = join(image_store, userid); - - try { - const img = sharp(buffer); - img.resize({ - height: 128, - width: 128, - fit: 'fill', - }); - const data = await img.png({ compressionLevel: 6 }).toBuffer(); - const image_file = await open(image_path, 'w', 0o666); - await image_file.write(data); - await image_file.close(); - } - catch (e) { - fastify.log.error(`Error: ${e}`); - reply.code(400); - return { status: 'error' }; - } - }); -}; - -export default route; - diff --git a/src/icons/src/run.ts b/src/icons/src/run.ts deleted file mode 100644 index d3d410f..0000000 --- a/src/icons/src/run.ts +++ /dev/null @@ -1,35 +0,0 @@ -// this sould only be used by the docker file ! - -import fastify, { FastifyInstance } from 'fastify'; -import app from './app'; - -const start = async () => { - const envToLogger = { - development: { - transport: { - target: 'pino-pretty', - options: { - translateTime: 'HH:MM:ss Z', - ignore: 'pid,hostname', - }, - }, - }, - production: true, - test: false, - }; - - const f: FastifyInstance = fastify({ logger: envToLogger.development }); - process.on('SIGTERM', () => { - f.log.info('Requested to shutdown'); - process.exit(134); - }); - try { - await f.register(app, {}); - await f.listen({ port: 80, host: '0.0.0.0' }); - } - catch (err) { - f.log.error(err); - process.exit(1); - } -}; -start(); diff --git a/src/icons/tsconfig.json b/src/icons/tsconfig.json deleted file mode 100644 index e6d24e2..0000000 --- a/src/icons/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": {}, - "include": ["src/**/*.ts"] -} diff --git a/src/icons/vite.config.js b/src/icons/vite.config.js deleted file mode 100644 index 2c172a5..0000000 --- a/src/icons/vite.config.js +++ /dev/null @@ -1,51 +0,0 @@ -import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; -import nodeExternals from 'rollup-plugin-node-externals'; -import path from 'node:path'; -import fs from 'node:fs'; - -function collectDeps(...pkgJsonPaths) { - const allDeps = new Set(); - for (const pkgPath of pkgJsonPaths) { - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); - for (const dep of Object.keys(pkg.dependencies || {})) { - allDeps.add(dep); - } - for (const peer of Object.keys(pkg.peerDependencies || {})) { - allDeps.add(peer); - } - } - return Array.from(allDeps); -} - -const externals = collectDeps( - './package.json', - '../@shared/package.json', -); - - -export default defineConfig({ - root: __dirname, - // service root - plugins: [tsconfigPaths(), nodeExternals()], - build: { - ssr: true, - outDir: 'dist', - emptyOutDir: true, - lib: { - entry: path.resolve(__dirname, 'src/run.ts'), - // adjust main entry - formats: ['cjs'], - // CommonJS for Node.js - fileName: () => 'index.js', - }, - rollupOptions: { - external: externals, - }, - target: 'node22', - // or whatever Node version you use - sourcemap: false, - minify: true, - // for easier debugging - }, -}); diff --git a/src/openapi.json b/src/openapi.json index d9b52b6..42cbe84 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -21,6 +21,143 @@ } ], "paths": { + "/api/auth/changePassword": { + "post": { + "operationId": "changePassword", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_password" + ], + "properties": { + "new_password": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "changePassword.success" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failed" + ] + }, + "msg": { + "enum": [ + "changePassword.failed.toolong", + "changePassword.failed.tooshort", + "changePassword.failed.invalid" + ] + } + } + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "notLoggedIn" + ] + }, + "msg": { + "enum": [ + "auth.noCookie", + "auth.invalidKind", + "auth.noUser", + "auth.invalid" + ] + } + } + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg" + ], + "properties": { + "kind": { + "enum": [ + "failed" + ] + }, + "msg": { + "enum": [ + "changePassword.failed.generic" + ] + } + } + } + } + } + } + }, + "tags": [ + "openapi_other" + ] + } + }, "/api/auth/disableOtp": { "put": { "operationId": "disableOtp", From d85192c0f3ded505e0866f3fcc8973c0237c16e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= <35407363+EniumRaphael@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:00:55 +0100 Subject: [PATCH 57/66] style(routes/info): removing debug print Removed console log for user information retrieval. --- src/user/src/routes/info.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/user/src/routes/info.ts b/src/user/src/routes/info.ts index 784ea23..ec46265 100644 --- a/src/user/src/routes/info.ts +++ b/src/user/src/routes/info.ts @@ -49,8 +49,6 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { if (isNullish(user)) { return res.makeResponse(404, 'failure', 'userinfo.failure.unknownUser'); } - console.log(user); - const payload = { name: user.name, id: user.id, From 393e7ee031e8429a6538c5e8e09cf6ef3dee0bff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:59:34 +0000 Subject: [PATCH 58/66] Initial plan From b6cb091f78cc06c3faab99add5dbff64676c33a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:01:30 +0000 Subject: [PATCH 59/66] Fix duplicate hidden attribute in profile.html line 43 Co-authored-by: EniumRaphael <35407363+EniumRaphael@users.noreply.github.com> --- frontend/src/pages/profile/profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html index 76c7565..53492d7 100644 --- a/frontend/src/pages/profile/profile.html +++ b/frontend/src/pages/profile/profile.html @@ -40,7 +40,7 @@ class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">Update
-