From 77324f6d8948b7cb32d5744872902050dedef532 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 19:02:49 +0200 Subject: [PATCH 01/32] core(package/linter): adding a linter for continuity propose - Added a linter make an mandatory code of conduct for developpers and have the same minimum --- eslint.config.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 9 ++++++++ 2 files changed, 64 insertions(+) create mode 100644 eslint.config.js create mode 100644 package.json diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5b8cab6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,55 @@ +import js from '@eslint/js'; +import ts from 'typescript-eslint'; + +export default [ + js.configs.recommended, + ...ts.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + }, + rules: { + 'arrow-spacing': ['warn', { before: true, after: true }], + 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': 'error', + 'comma-style': 'error', + curly: ['error', 'multi-line', 'consistent'], + 'dot-location': ['error', 'property'], + 'handle-callback-err': 'off', + indent: ['error', 'tab'], + 'keyword-spacing': 'error', + 'max-nested-callbacks': ['error', { max: 4 }], + 'max-statements-per-line': ['error', { max: 2 }], + 'no-console': 'off', + 'no-empty-function': 'error', + 'no-floating-decimal': 'error', + 'no-inline-comments': 'error', + 'no-lonely-if': 'error', + 'no-multi-spaces': 'error', + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }], + 'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }], + 'no-trailing-spaces': ['error'], + 'no-var': 'error', + 'no-undef': 'off', + 'object-curly-spacing': ['error', 'always'], + 'prefer-const': 'error', + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'space-before-blocks': 'error', + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], + 'space-in-parens': 'error', + 'space-infix-ops': 'error', + 'space-unary-ops': 'error', + 'spaced-comment': 'error', + yoda: 'error', + }, + }, +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..f995973 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "@eslint/js": "^9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^9.36.0", + "typescript-eslint": "^8.44.1" + } +} From b56906b5575c30209b57815a370106de35aaa346 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 19:03:51 +0200 Subject: [PATCH 02/32] style(shared): auto-correction of the linter - using pnpm eslint --fix ./src --- src/@shared/src/auth/index.ts | 91 +++++++++++---------- src/@shared/src/database/index.ts | 23 +++--- src/@shared/src/database/mixin/_base.ts | 54 ++++++------ src/@shared/src/database/mixin/_template.ts | 28 +++---- src/@shared/src/database/mixin/user.ts | 33 ++++---- src/@shared/src/utils/index.ts | 22 ++--- 6 files changed, 126 insertions(+), 125 deletions(-) diff --git a/src/@shared/src/auth/index.ts b/src/@shared/src/auth/index.ts index c41aa26..b814771 100644 --- a/src/@shared/src/auth/index.ts +++ b/src/@shared/src/auth/index.ts @@ -1,23 +1,23 @@ -import OTP from "otp"; -import cookie from "@fastify/cookie"; -import fastifyJwt from "@fastify/jwt"; -import fp from "fastify-plugin"; -import { FastifyPluginAsync, preValidationAsyncHookHandler } from "fastify"; -import { Static, Type } from "@sinclair/typebox"; -import { UserId } from "@shared/database/mixin/user"; -import { useDatabase } from "@shared/database"; -import { isNullish, makeResponse } from "@shared/utils"; +import OTP from 'otp'; +import cookie from '@fastify/cookie'; +import fastifyJwt from '@fastify/jwt'; +import fp from 'fastify-plugin'; +import { FastifyPluginAsync, preValidationAsyncHookHandler } from 'fastify'; +import { Static, Type } from '@sinclair/typebox'; +import { UserId } from '@shared/database/mixin/user'; +import { useDatabase } from '@shared/database'; +import { isNullish, makeResponse } from '@shared/utils'; -const kRouteAuthDone = Symbol("shared-route-auth-done"); +const kRouteAuthDone = Symbol('shared-route-auth-done'); type AuthedUser = { id: UserId; name: string; }; -declare module "fastify" { +declare module 'fastify' { export interface FastifyInstance { - signJwt: (kind: "auth" | "otp", who: string) => string; + signJwt: (kind: 'auth' | 'otp', who: string) => string; [s: symbol]: boolean; } export interface FastifyRequest { @@ -33,10 +33,10 @@ let jwtAdded = false; export const jwtPlugin = fp(async (fastify, _opts) => { if (jwtAdded) return; jwtAdded = true; - let env = process.env.JWT_SECRET; - if (isNullish(env)) throw "JWT_SECRET is not defined"; - if (!fastify.hasDecorator("signJwt")) { - void fastify.decorate("signJwt", (kind, who) => + const env = process.env.JWT_SECRET; + if (isNullish(env)) throw 'JWT_SECRET is not defined'; + if (!fastify.hasDecorator('signJwt')) { + void fastify.decorate('signJwt', (kind, who) => fastify.jwt.sign({ kind, who, createdAt: Date.now() }), ); void fastify.register(fastifyJwt, { @@ -48,16 +48,16 @@ export const jwtPlugin = fp(async (fastify, _opts) => { export const JwtType = Type.Object({ kind: Type.Union([ - Type.Const("otp", { - description: "the token is only valid for otp call", + Type.Const('otp', { + description: 'the token is only valid for otp call', }), - Type.Const("auth", { - description: "the token is valid for authentication", + Type.Const('auth', { + description: 'the token is valid for authentication', }), ]), - who: Type.String({ description: "the login of the user" }), + who: Type.String({ description: 'the login of the user' }), createdAt: Type.Integer({ - description: "Unix timestamp of when the token as been created at", + description: 'Unix timestamp of when the token as been created at', }), }); @@ -65,52 +65,57 @@ export type JwtType = Static; let authAdded = false; export const authPlugin = fp(async (fastify, _opts) => { - if (authAdded) return void console.log("skipping"); + if (authAdded) return void console.log('skipping'); authAdded = true; await fastify.register(useDatabase as any, {}); await fastify.register(jwtPlugin as any, {}); await fastify.register(cookie); - if (!fastify.hasRequestDecorator("authUser")) - fastify.decorateRequest("authUser", undefined); - fastify.addHook("onRoute", (routeOpts) => { + if (!fastify.hasRequestDecorator('authUser')) {fastify.decorateRequest('authUser', undefined);} + fastify.addHook('onRoute', (routeOpts) => { if ( routeOpts.config?.requireAuth && !(routeOpts as any)[kRouteAuthDone] ) { - let f: preValidationAsyncHookHandler = async function(req, res) { + const f: preValidationAsyncHookHandler = async function(req, res) { try { - if (isNullish(req.cookies.token)) + if (isNullish(req.cookies.token)) { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.noCookie")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.noCookie')), ); - let tok = this.jwt.verify(req.cookies.token); - if (tok.kind != "auth") + } + const tok = this.jwt.verify(req.cookies.token); + if (tok.kind != 'auth') { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.invalidKind")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.invalidKind')), ); - let user = this.db.getUserFromName(tok.who); - if (isNullish(user)) + } + const user = this.db.getUserFromName(tok.who); + if (isNullish(user)) { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.noUser")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.noUser')), ); + } req.authUser = { id: user.id, name: tok.who }; - } catch { + } + catch { return res - .clearCookie("token") - .send(JSON.stringify(makeResponse("notLoggedIn", "auth.invalid"))); + .clearCookie('token') + .send(JSON.stringify(makeResponse('notLoggedIn', 'auth.invalid'))); } }; if (!routeOpts.preValidation) { routeOpts.preValidation = [f]; - } else if (Array.isArray(routeOpts.preValidation)) { + } + else if (Array.isArray(routeOpts.preValidation)) { routeOpts.preValidation.push(f); - } else { + } + else { routeOpts.preValidation = [routeOpts.preValidation, f]; } diff --git a/src/@shared/src/database/index.ts b/src/@shared/src/database/index.ts index d77b176..d3bd9cf 100644 --- a/src/@shared/src/database/index.ts +++ b/src/@shared/src/database/index.ts @@ -1,8 +1,8 @@ -import fp from 'fastify-plugin' -import { FastifyInstance, FastifyPluginAsync } from 'fastify' +import fp from 'fastify-plugin'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; -import { Database as DbImpl } from "./mixin/_base"; -import { UserImpl, IUserDb } from "./mixin/user"; +import { Database as DbImpl } from './mixin/_base'; +import { UserImpl, IUserDb } from './mixin/user'; import { isNullish } from '@shared/utils'; @@ -22,16 +22,13 @@ let dbAdded = false; export const useDatabase = fp(async function( f: FastifyInstance, _options: {}) { - if (dbAdded) - return; + if (dbAdded) {return;} dbAdded = true; - let path = process.env.DATABASE_DIR; - if (isNullish(path)) - throw "env `DATABASE_DIR` not defined"; - f.log.info(`Opening database with path: ${path}/database.db`) - let db: Database = new DbImpl(`${path}/database.db`) as Database; - if (!f.hasDecorator("db")) - f.decorate('db', db); + const path = process.env.DATABASE_DIR; + if (isNullish(path)) {throw 'env `DATABASE_DIR` not defined';} + f.log.info(`Opening database with path: ${path}/database.db`); + const db: Database = new DbImpl(`${path}/database.db`) as Database; + if (!f.hasDecorator('db')) {f.decorate('db', db);} }); export default useDatabase; diff --git a/src/@shared/src/database/mixin/_base.ts b/src/@shared/src/database/mixin/_base.ts index 1d601f1..e039883 100644 --- a/src/@shared/src/database/mixin/_base.ts +++ b/src/@shared/src/database/mixin/_base.ts @@ -1,57 +1,57 @@ -import sqlite from "better-sqlite3"; +import sqlite from 'better-sqlite3'; // @ts-ignore: this file is included using vite, typescript doesn't know how to include this... -import initSql from "../init.sql?raw" +import initSql from '../init.sql?raw'; export type SqliteReturn = object | undefined; // Only way to use the database. Everything must be done through this. // Prefer to use prepared statement `using this.db.prepare` // -// this is the base, meaning that no actual query are made from this file. +// this is the base, meaning that no actual query are made from this file. // To create a new query function, go open another file, create a class that inherit from this class // in the `index.ts` file, import the new class, and make the `Database` class inherit it export class Database { - private db: sqlite.Database; - private st: Map = new Map(); + private db: sqlite.Database; + private st: Map = new Map(); - /** + /** * Create a new instance of the database, and init it to a known state * the file ./init.sql will be ran onto the database, creating any table that might be missing */ - constructor(db_path: string) { - this.db = sqlite(db_path, {}); - this.db.pragma('journal_mode = WAL'); - this.db.transaction(() => this.db.exec(initSql))(); - } + constructor(db_path: string) { + this.db = sqlite(db_path, {}); + this.db.pragma('journal_mode = WAL'); + this.db.transaction(() => this.db.exec(initSql))(); + } - /** + /** * close the database */ - public destroy(): void { - // remove any statement from the cache - this.st.clear(); - // close the database - this.db?.close(); - } + public destroy(): void { + // remove any statement from the cache + this.st.clear(); + // close the database + this.db?.close(); + } - /** + /** * use this to create queries. This will create statements (kinda expensive) and cache them * since they will be cached, this means that they are only created once, * otherwise they'll be just spat out from the cache - * the statements are cached by the {query} argument, + * the statements are cached by the {query} argument, * meaning that if you try to make two identiqual statement, but with different {query} they won't be cached * * @example this.prepare('SELECT * FROM users WHERE id = ?') * @example this.prepare('SELECT * FROM users LIMIT 100 OFFSET ?') */ - protected prepare(query: string): sqlite.Statement { - let st = this.st.get(query); - if (st !== undefined) return st; + protected prepare(query: string): sqlite.Statement { + let st = this.st.get(query); + if (st !== undefined) return st; - st = this.db.prepare(query); - this.st.set(query, st); - return st; - } + st = this.db.prepare(query); + this.st.set(query, st); + return st; + } } diff --git a/src/@shared/src/database/mixin/_template.ts b/src/@shared/src/database/mixin/_template.ts index 05ced1e..c7fbee5 100644 --- a/src/@shared/src/database/mixin/_template.ts +++ b/src/@shared/src/database/mixin/_template.ts @@ -1,4 +1,4 @@ -import type { Database } from "./_base"; +import type { Database } from './_base'; // never use this directly @@ -9,28 +9,28 @@ export interface ITemplateDb extends Database { }; export const UserImpl: Omit = { - /** + /** * whole function description * * @param id the argument description * * @returns what does the function return ? */ - normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | undefined { - void id; - return undefined; - }, - /** + normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | undefined { + void id; + return undefined; + }, + /** * whole function description * * @param id the argument description * * @returns what does the function return ? */ - async asyncFunction(this: ITemplateDb, id: TemplateId): Promise { - void id; - return undefined; - }, + async asyncFunction(this: ITemplateDb, id: TemplateId): Promise { + void id; + return undefined; + }, }; export type TemplateId = number & { readonly __brand: unique symbol }; @@ -43,13 +43,13 @@ export type TemplateData = { // this function will be able to be called from everywhere export async function freeFloatingExportedFunction(): Promise { - return false; + return false; } // this function will never be able to be called outside of this module async function privateFunction(): Promise { - return undefined + return undefined; } -//silence warnings +// silence warnings void privateFunction; diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index 1fc54fc..6885100 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -1,7 +1,7 @@ -import type { Database, SqliteReturn } from "./_base"; -import { Otp } from "@shared/auth"; -import { isNullish } from "@shared/utils"; -import * as bcrypt from "bcrypt"; +import type { Database, SqliteReturn } from './_base'; +import { Otp } from '@shared/auth'; +import { isNullish } from '@shared/utils'; +import * as bcrypt from 'bcrypt'; // never use this directly @@ -38,7 +38,7 @@ export const UserImpl: Omit = { getUserFromName(this: IUserDb, name: string): User | undefined { return userFromRow( this.prepare( - "SELECT * FROM user WHERE name = @name LIMIT 1", + 'SELECT * FROM user WHERE name = @name LIMIT 1', ).get({ name }), ); }, @@ -52,7 +52,7 @@ export const UserImpl: Omit = { */ getUserFromRawId(this: IUserDb, id: number): User | undefined { return userFromRow( - this.prepare("SELECT * FROM user WHERE id = @id LIMIT 1").get({ + this.prepare('SELECT * FROM user WHERE id = @id LIMIT 1').get({ id, }) as SqliteReturn, ); @@ -70,7 +70,7 @@ export const UserImpl: Omit = { password = await hashPassword(password); return userFromRow( this.prepare( - "INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *", + 'INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *', ).get({ name, password }), ); }, @@ -88,30 +88,29 @@ export const UserImpl: Omit = { password = await hashPassword(password); return userFromRow( this.prepare( - "UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *", + 'UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *', ).get({ password, id }) as SqliteReturn, ); }, getUserOtpSecret(this: IUserDb, id: UserId): string | undefined { - let otp: any = this.prepare("SELECT otp FROM user WHERE id = @id LIMIT 1").get({ id }) as SqliteReturn; + const otp: any = this.prepare('SELECT otp FROM user WHERE id = @id LIMIT 1').get({ id }) as SqliteReturn; if (isNullish(otp?.otp)) return undefined; return otp.otp; }, ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined { - let otp = this.getUserOtpSecret(id); - if (!isNullish(otp)) - return otp; - let otpGen = new Otp(); - const res: any = this.prepare("UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp") + const otp = this.getUserOtpSecret(id); + if (!isNullish(otp)) {return otp;} + const otpGen = new Otp(); + const res: any = this.prepare('UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp') .get({ id, otp: otpGen.secret }); return res?.otp; }, deleteUserOtpSecret(this: IUserDb, id: UserId): void { - this.prepare("UPDATE OR IGNORE user SET otp = NULL WHERE id = @id").run({ id }); - } + this.prepare('UPDATE OR IGNORE user SET otp = NULL WHERE id = @id').run({ id }); + }, }; export type UserId = number & { readonly __brand: unique symbol }; @@ -163,4 +162,4 @@ function userFromRow(row: any): User | undefined { password: row.password || undefined, otp: row.otp || undefined, }; -} +} diff --git a/src/@shared/src/utils/index.ts b/src/@shared/src/utils/index.ts index 350b93e..3918b27 100644 --- a/src/@shared/src/utils/index.ts +++ b/src/@shared/src/utils/index.ts @@ -1,4 +1,4 @@ -import { Type } from "@sinclair/typebox"; +import { Type } from '@sinclair/typebox'; /** * @description Represent a message key @@ -20,21 +20,21 @@ export type ResponseBase = { /** * @description Builds a response from a `kind`, `key` and an arbitrary payload - * + * * * USE THIS FUNCTION TO ALLOW GREPING :) * * * @example makeResponse("failure", "login.failure.invalid") * @example makeResponse("success", "login.success", { token: "supersecrettoken" }) */ export function makeResponse(kind: string, key: MessageKey, payload?: T): ResponseBase { - console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`) - return { kind, msg: key, payload } + console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`); + return { kind, msg: key, payload }; } -/** +/** * @description Create a typebox Type for a response. - * + * * @example typeResponse("failure", ["login.failure.invalid", "login.failure.generic", "login.failure.missingPassword"]) * @example typeResponse("otpRequired", "login.otpRequired", { token: Type.String() }) * @example typeResponse("success", "login.success", { token: Type.String() }) @@ -43,7 +43,8 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo let tKey; if (key instanceof Array) { tKey = Type.Union(key.map(l => Type.Const(l))); - } else { + } + else { tKey = Type.Const(key); } @@ -51,10 +52,9 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo kind: Type.Const(kind), msg: tKey, }; - if (payload !== undefined) - Object.assign(Ty, { payload: Type.Object(payload) }) + if (payload !== undefined) {Object.assign(Ty, { payload: Type.Object(payload) });} - return Type.Object(Ty) + return Type.Object(Ty); } /** @@ -69,5 +69,5 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo * @example assert_equal(isNullish(false), false); */ export function isNullish(v: T | undefined | null): v is (null | undefined) { - return v === null || v === undefined + return v === null || v === undefined; } From 38013b77d30da2cb5ce3f45d63a09a965c8c710b Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 19:03:59 +0200 Subject: [PATCH 03/32] style(auth): auto-correction of the linter - using pnpm eslint --fix ./src --- src/auth/extra/login_demo.js | 101 +++++++++++++++--------------- src/auth/src/app.ts | 34 +++++----- src/auth/src/plugins/sensible.ts | 8 +-- src/auth/src/routes/disableOtp.ts | 19 +++--- src/auth/src/routes/enableOtp.ts | 28 ++++----- src/auth/src/routes/login.ts | 34 +++++----- src/auth/src/routes/logout.ts | 6 +- src/auth/src/routes/otp.ts | 57 ++++++++--------- src/auth/src/routes/signin.ts | 59 ++++++++--------- src/auth/src/routes/statusOtp.ts | 30 +++++---- src/auth/src/routes/whoami.ts | 19 +++--- src/auth/src/run.ts | 19 +++--- src/auth/vite.config.js | 16 ++--- 13 files changed, 207 insertions(+), 223 deletions(-) diff --git a/src/auth/extra/login_demo.js b/src/auth/extra/login_demo.js index 188e749..c20f847 100644 --- a/src/auth/extra/login_demo.js +++ b/src/auth/extra/login_demo.js @@ -1,107 +1,104 @@ const headers = { 'Accept': 'application/json', - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }; -const tUsername = document.querySelector("#t-username") +const tUsername = document.querySelector('#t-username'); -const iUsername = document.querySelector("#i-username"); -const iPassword = document.querySelector("#i-password"); -const iOtp = document.querySelector("#i-otp"); +const iUsername = document.querySelector('#i-username'); +const iPassword = document.querySelector('#i-password'); +const iOtp = document.querySelector('#i-otp'); -const bOtpSend = document.querySelector("#b-otpSend"); -const bLogin = document.querySelector("#b-login"); -const bLogout = document.querySelector("#b-logout"); -const bSignin = document.querySelector("#b-signin"); -const bWhoami = document.querySelector("#b-whoami"); +const bOtpSend = document.querySelector('#b-otpSend'); +const bLogin = document.querySelector('#b-login'); +const bLogout = document.querySelector('#b-logout'); +const bSignin = document.querySelector('#b-signin'); +const bWhoami = document.querySelector('#b-whoami'); -const bOtpStatus = document.querySelector("#b-otpStatus"); -const bOtpEnable = document.querySelector("#b-otpEnable"); -const bOtpDisable = document.querySelector("#b-otpDisable"); +const bOtpStatus = document.querySelector('#b-otpStatus'); +const bOtpEnable = document.querySelector('#b-otpEnable'); +const bOtpDisable = document.querySelector('#b-otpDisable'); -const dResponse = document.querySelector("#d-response"); +const dResponse = document.querySelector('#d-response'); function setResponse(obj) { - let obj_str = JSON.stringify(obj, null, 4); + const obj_str = JSON.stringify(obj, null, 4); dResponse.innerText = obj_str; } let otpToken = null; -bOtpSend.addEventListener("click", async () => { - let res = await fetch("/api/auth/otp", { method: "POST", body: JSON.stringify({ code: iOtp.value, token: otpToken }), headers }); +bOtpSend.addEventListener('click', async () => { + const res = await fetch('/api/auth/otp', { method: 'POST', body: JSON.stringify({ code: iOtp.value, token: otpToken }), headers }); const json = await res.json(); setResponse(json); - if (json.kind === "success") { - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token}`; + if (json.kind === 'success') { + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token}`;} } }); -bOtpStatus.addEventListener("click", async () => { - let res = await fetch("/api/auth/statusOtp"); +bOtpStatus.addEventListener('click', async () => { + const res = await fetch('/api/auth/statusOtp'); const json = await res.json(); setResponse(json); }); -bOtpEnable.addEventListener("click", async () => { - let res = await fetch("/api/auth/enableOtp", { method: "PUT" }); +bOtpEnable.addEventListener('click', async () => { + const res = await fetch('/api/auth/enableOtp', { method: 'PUT' }); const json = await res.json(); setResponse(json); }); -bOtpDisable.addEventListener("click", async () => { - let res = await fetch("/api/auth/disableOtp", { method: "PUT" }); +bOtpDisable.addEventListener('click', async () => { + const res = await fetch('/api/auth/disableOtp', { method: 'PUT' }); const json = await res.json(); setResponse(json); }); -bWhoami.addEventListener("click", async () => { - let username = ""; +bWhoami.addEventListener('click', async () => { + let username = ''; try { - let res = await fetch("/api/auth/whoami"); + const res = await fetch('/api/auth/whoami'); const json = await res.json(); setResponse(json); - if (json?.kind === "success") - username = json?.payload?.name; - else - username = `` - } catch { - username = `` + if (json?.kind === 'success') {username = json?.payload?.name;} + else {username = ``;} + } + catch { + username = ''; } tUsername.innerText = username; }); -bLogin.addEventListener("click", async () => { +bLogin.addEventListener('click', async () => { const name = iUsername.value; const password = iPassword.value; - let res = await fetch("/api/auth/login", { method: "POST", body: JSON.stringify({ name, password }), headers }); - let json = await res.json(); - if (json?.kind === "otpRequired") { + const res = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify({ name, password }), headers }); + const json = await res.json(); + if (json?.kind === 'otpRequired') { otpToken = json?.payload?.token; - } else if (json?.kind === "success") { - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token}`; + } + else if (json?.kind === 'success') { + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token}`;} } setResponse(json); -}) +}); -bLogout.addEventListener("click", async () => { - let res = await fetch("/api/auth/logout", { method: "POST" }); +bLogout.addEventListener('click', async () => { + const res = await fetch('/api/auth/logout', { method: 'POST' }); setResponse(await res.json()); -}) +}); -bSignin.addEventListener("click", async () => { +bSignin.addEventListener('click', async () => { const name = iUsername.value; const password = iPassword.value; - let res = await fetch("/api/auth/signin", { method: "POST", body: JSON.stringify({ name, password }), headers }); - let json = await res.json(); - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token};`; + const res = await fetch('/api/auth/signin', { method: 'POST', body: JSON.stringify({ name, password }), headers }); + const json = await res.json(); + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token};`;} setResponse(json); -}) +}); diff --git a/src/auth/src/app.ts b/src/auth/src/app.ts index 0aba7d4..077e5c5 100644 --- a/src/auth/src/app.ts +++ b/src/auth/src/app.ts @@ -1,10 +1,10 @@ -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 auth from '@shared/auth' +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 auth from '@shared/auth'; // @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); @@ -21,12 +21,12 @@ declare module 'fastify' { const app: FastifyPluginAsync = async ( fastify, - opts + opts, ): Promise => { - await fastify.register(db.useDatabase as any, {}) - await fastify.register(auth.jwtPlugin as any, {}) - await fastify.register(auth.authPlugin as any, {}) - + await fastify.register(db.useDatabase as any, {}); + await fastify.register(auth.jwtPlugin as any, {}); + await fastify.register(auth.authPlugin as any, {}); + // Place here your custom code! for (const plugin of Object.values(plugins)) { void fastify.register(plugin as any, {}); @@ -35,9 +35,9 @@ const app: FastifyPluginAsync = async ( void fastify.register(route as any, {}); } - void fastify.register(fastifyFormBody, {}) - void fastify.register(fastifyMultipart, {}) -} + void fastify.register(fastifyFormBody, {}); + void fastify.register(fastifyMultipart, {}); +}; -export default app -export { app } +export default app; +export { app }; diff --git a/src/auth/src/plugins/sensible.ts b/src/auth/src/plugins/sensible.ts index 5be2c8a..8c2093c 100644 --- a/src/auth/src/plugins/sensible.ts +++ b/src/auth/src/plugins/sensible.ts @@ -1,5 +1,5 @@ -import fp from 'fastify-plugin' -import sensible, { FastifySensibleOptions } from '@fastify/sensible' +import fp from 'fastify-plugin'; +import sensible, { FastifySensibleOptions } from '@fastify/sensible'; /** * This plugins adds some utilities to handle http errors @@ -7,5 +7,5 @@ import sensible, { FastifySensibleOptions } from '@fastify/sensible' * @see https://github.com/fastify/fastify-sensible */ export default fp(async (fastify) => { - fastify.register(sensible) -}) + fastify.register(sensible); +}); diff --git a/src/auth/src/routes/disableOtp.ts b/src/auth/src/routes/disableOtp.ts index 7c09325..38dd0c8 100644 --- a/src/auth/src/routes/disableOtp.ts +++ b/src/auth/src/routes/disableOtp.ts @@ -1,25 +1,24 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { makeResponse, typeResponse, isNullish } from "@shared/utils" +import { Static, Type } from '@sinclair/typebox'; +import { makeResponse, typeResponse, isNullish } from '@shared/utils'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "disableOtp.success"), - typeResponse("failure", "disableOtp.failure.generic") + typeResponse('success', 'disableOtp.success'), + typeResponse('failure', 'disableOtp.failure.generic'), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.put( - "/api/auth/disableOtp", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/disableOtp', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "disableOtp.failure.generic"); + if (isNullish(req.authUser)) {return makeResponse('failure', 'disableOtp.failure.generic');} this.db.deleteUserOtpSecret(req.authUser.id); - return makeResponse("success", "disableOtp.success"); + return makeResponse('success', 'disableOtp.success'); }, ); }; diff --git a/src/auth/src/routes/enableOtp.ts b/src/auth/src/routes/enableOtp.ts index 3d053f0..ec25acb 100644 --- a/src/auth/src/routes/enableOtp.ts +++ b/src/auth/src/routes/enableOtp.ts @@ -1,29 +1,27 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" -import { Otp } from "@shared/auth"; +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; +import { Otp } from '@shared/auth'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "enableOtp.success", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }), - typeResponse("failure", ["enableOtp.failure.noUser", "enableOtp.failure.noSecret"]) + typeResponse('success', 'enableOtp.success', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), + typeResponse('failure', ['enableOtp.failure.noUser', 'enableOtp.failure.noSecret']), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.put( - "/api/auth/enableOtp", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/enableOtp', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "enableOtp.failure.noUser"); - let otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); - if (isNullish(otpSecret)) - return makeResponse("failure", "enableOtp.failure.noSecret"); - let otp = new Otp({ secret: otpSecret }); - return makeResponse("success", "enableOtp.success", { url: otp.totpURL }); + if (isNullish(req.authUser)) {return makeResponse('failure', 'enableOtp.failure.noUser');} + const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); + if (isNullish(otpSecret)) {return makeResponse('failure', 'enableOtp.failure.noSecret');} + const otp = new Otp({ secret: otpSecret }); + return makeResponse('success', 'enableOtp.success', { url: otp.totpURL }); }, ); }; diff --git a/src/auth/src/routes/login.ts b/src/auth/src/routes/login.ts index c869814..b61d6ab 100644 --- a/src/auth/src/routes/login.ts +++ b/src/auth/src/routes/login.ts @@ -1,8 +1,8 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils" -import { verifyUserPassword } from "@shared/database/mixin/user"; +import { Static, Type } from '@sinclair/typebox'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; +import { verifyUserPassword } from '@shared/database/mixin/user'; export const LoginReq = Type.Object({ name: Type.String(), @@ -12,9 +12,9 @@ export const LoginReq = Type.Object({ export type LoginReq = Static; export const LoginRes = Type.Union([ - typeResponse("failed", ["login.failed.generic", "login.failed.invalid"]), - typeResponse("otpRequired", "login.otpRequired", { token: Type.String({ description: "JWT to send with the OTP to finish login" }) }), - typeResponse("success", "login.success", { token: Type.String({ description: "JWT that represent a logged in user" }) }), + typeResponse('failed', ['login.failed.generic', 'login.failed.invalid']), + typeResponse('otpRequired', 'login.otpRequired', { token: Type.String({ description: 'JWT to send with the OTP to finish login' }) }), + typeResponse('success', 'login.success', { token: Type.String({ description: 'JWT that represent a logged in user' }) }), ]); @@ -22,34 +22,32 @@ export type LoginRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.post<{ Body: LoginReq; Response: LoginRes }>( - "/api/auth/login", - { schema: { body: LoginReq, response: { "2xx": LoginRes } }, }, + '/api/auth/login', + { schema: { body: LoginReq, response: { '2xx': LoginRes } } }, async function(req, _res) { try { - let { name, password } = req.body; - let user = this.db.getUserFromName(name); + const { name, password } = req.body; + const user = this.db.getUserFromName(name); // does the user exist // does it have a password setup ? - if (isNullish(user?.password)) - return makeResponse("failed", "login.failed.invalid"); + if (isNullish(user?.password)) {return makeResponse('failed', 'login.failed.invalid');} // does the password he provided match the one we have - if (!(await verifyUserPassword(user, password))) - return makeResponse("failed", "login.failed.invalid"); + if (!(await verifyUserPassword(user, password))) {return makeResponse('failed', 'login.failed.invalid');} // does the user has 2FA up ? if (!isNullish(user.otp)) { // yes -> we ask them to fill it, // send them somehting to verify that they indeed passed throught the user+password phase - return makeResponse("otpRequired", "login.otpRequired", { token: this.signJwt("otp", user.name) }); + return makeResponse('otpRequired', 'login.otpRequired', { token: this.signJwt('otp', user.name) }); } // every check has been passed, they are now logged in, using this token to say who they are... - return makeResponse("success", "login.success", { token: this.signJwt("auth", user.name) }); + return makeResponse('success', 'login.success', { token: this.signJwt('auth', user.name) }); } catch { - return makeResponse("failed", "login.failed.generic"); + return makeResponse('failed', 'login.failed.generic'); } }, ); diff --git a/src/auth/src/routes/logout.ts b/src/auth/src/routes/logout.ts index 64a5734..5e59c6e 100644 --- a/src/auth/src/routes/logout.ts +++ b/src/auth/src/routes/logout.ts @@ -1,10 +1,10 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.post( - "/api/auth/logout", + '/api/auth/logout', async function(_req, res) { - return res.clearCookie("token").send("{}") + return res.clearCookie('token').send('{}'); }, ); }; diff --git a/src/auth/src/routes/otp.ts b/src/auth/src/routes/otp.ts index 8266d0e..5d49b3f 100644 --- a/src/auth/src/routes/otp.ts +++ b/src/auth/src/routes/otp.ts @@ -1,19 +1,19 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { JwtType, Otp } from "@shared/auth"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils"; +import { Static, Type } from '@sinclair/typebox'; +import { JwtType, Otp } from '@shared/auth'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; const OtpReq = Type.Object({ - token: Type.String({ description: "The token given at the login phase" }), - code: Type.String({ description: "The OTP given by the user" }), + token: Type.String({ description: 'The token given at the login phase' }), + code: Type.String({ description: 'The OTP given by the user' }), }); type OtpReq = Static; const OtpRes = Type.Union([ - typeResponse("failed", ["otp.failed.generic", "otp.failed.invalid", "otp.failed.timeout", "otp.failed.noSecret"]), - typeResponse("success", "otp.success", { token: Type.String({ description: "the JWT Token" }) }), + typeResponse('failed', ['otp.failed.generic', 'otp.failed.invalid', 'otp.failed.timeout', 'otp.failed.noSecret']), + typeResponse('success', 'otp.success', { token: Type.String({ description: 'the JWT Token' }) }), ]); type OtpRes = Static; @@ -22,34 +22,34 @@ const OTP_TOKEN_TIMEOUT_SEC = 120; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.post<{ Body: OtpReq }>( - "/api/auth/otp", - { schema: { body: OtpReq, response: { "2xx": OtpRes } } }, + '/api/auth/otp', + { schema: { body: OtpReq, response: { '2xx': OtpRes } } }, async function(req, _res) { try { const { token, code } = req.body; // lets try to decode+verify the jwt - let dJwt = this.jwt.verify(token); + const dJwt = this.jwt.verify(token); // is the jwt a valid `otp` jwt ? - if (dJwt.kind != "otp") - // no ? fuck off then - return makeResponse("failed", "otp.failed.invalid"); + if (dJwt.kind != 'otp') + // no ? fuck off then + {return makeResponse('failed', 'otp.failed.invalid');} // is it too old ? if (dJwt.createdAt + OTP_TOKEN_TIMEOUT_SEC * 1000 < Date.now()) - // yes ? fuck off then, redo the password - return makeResponse("failed", "otp.failed.timeout"); + // yes ? fuck off then, redo the password + {return makeResponse('failed', 'otp.failed.timeout');} // get the Otp sercret from the db - let user = this.db.getUserFromName(dJwt.who); + const user = this.db.getUserFromName(dJwt.who); if (isNullish(user?.otp)) - // oops, either no user, or user without otpSecret - // fuck off - return makeResponse("failed", "otp.failed.noSecret"); + // oops, either no user, or user without otpSecret + // fuck off + {return makeResponse('failed', 'otp.failed.noSecret');} // good lets now verify the token you gave us is the correct one... - let otpHandle = new Otp({ secret: user.otp }); + const otpHandle = new Otp({ secret: user.otp }); - let now = Date.now(); + const now = Date.now(); const tokens = [ // we also get the last code, to mitiage the delay between client<->server roundtrip... otpHandle.totp(now - 30 * 1000), @@ -59,13 +59,14 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { // checking if any of the array match if (tokens.some((c) => c === code)) - // they do ! - // gg you are now logged in ! - return makeResponse("success", "otp.success", { token: this.signJwt("auth", dJwt.who) }); - } catch { - return makeResponse("failed", "otp.failed.generic"); + // they do ! + // gg you are now logged in ! + {return makeResponse('success', 'otp.success', { token: this.signJwt('auth', dJwt.who) });} } - return makeResponse("failed", "otp.failed.generic"); + catch { + return makeResponse('failed', 'otp.failed.generic'); + } + return makeResponse('failed', 'otp.failed.generic'); }, ); }; diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index e6e84bb..02cb9ba 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -1,7 +1,7 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils"; +import { Static, Type } from '@sinclair/typebox'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/; @@ -13,51 +13,44 @@ const SignInReq = Type.Object({ type SignInReq = Static; const SignInRes = Type.Union([ - typeResponse("failed", [ - "signin.failed.generic", - "signin.failed.username.existing", - "signin.failed.username.toolong", - "signin.failed.username.tooshort", - "signin.failed.username.invalid", - "signin.failed.password.toolong", - "signin.failed.password.tooshort", - "signin.failed.password.invalid", + typeResponse('failed', [ + 'signin.failed.generic', + 'signin.failed.username.existing', + 'signin.failed.username.toolong', + 'signin.failed.username.tooshort', + 'signin.failed.username.invalid', + 'signin.failed.password.toolong', + 'signin.failed.password.tooshort', + 'signin.failed.password.invalid', ]), - typeResponse("success", "signin.success", { token: Type.String({ description: "the JWT token" }) }), -]) + typeResponse('success', 'signin.success', { token: Type.String({ description: 'the JWT token' }) }), +]); type SignInRes = Static; const route: FastifyPluginAsync = async (fastify, opts): Promise => { fastify.post<{ Body: SignInReq }>( - "/api/auth/signin", - { schema: { body: SignInReq, response: { "200": SignInRes, "5xx": Type.Object({}) } }, }, + '/api/auth/signin', + { schema: { body: SignInReq, response: { '200': SignInRes, '5xx': Type.Object({}) } } }, async function(req, res) { const { name, password } = req.body; - if (name.length < 4) - return makeResponse("failed", "signin.failed.username.tooshort"); - if (name.length > 32) - return makeResponse("failed", "signin.failed.username.toolong"); - if (!USERNAME_CHECK.test(name)) - return makeResponse("failed", "signin.failed.username.invalid"); + if (name.length < 4) {return makeResponse('failed', 'signin.failed.username.tooshort');} + if (name.length > 32) {return makeResponse('failed', 'signin.failed.username.toolong');} + if (!USERNAME_CHECK.test(name)) {return makeResponse('failed', 'signin.failed.username.invalid');} // username if good now :) - if (password.length < 8) - return makeResponse("failed", "signin.failed.password.tooshort"); - if (password.length > 64) - return makeResponse("failed", "signin.failed.password.toolong"); + if (password.length < 8) {return makeResponse('failed', 'signin.failed.password.tooshort');} + if (password.length > 64) {return makeResponse('failed', 'signin.failed.password.toolong');} // password is good too ! - if (this.db.getUserFromName(name) !== undefined) - return makeResponse("failed", "signin.failed.username.existing"); - let u = await this.db.createUser(name, password); - if (isNullish(u)) - return makeResponse("failed", "signin.failed.generic"); + if (this.db.getUserFromName(name) !== undefined) {return makeResponse('failed', 'signin.failed.username.existing');} + const u = await this.db.createUser(name, password); + if (isNullish(u)) {return makeResponse('failed', 'signin.failed.generic');} // every check has been passed, they are now logged in, using this token to say who they are... - let userToken = this.signJwt('auth', u.name); - return makeResponse("success", "signin.success", { token: userToken }); + const userToken = this.signJwt('auth', u.name); + return makeResponse('success', 'signin.success', { token: userToken }); }, ); }; diff --git a/src/auth/src/routes/statusOtp.ts b/src/auth/src/routes/statusOtp.ts index d79fd1d..2f71872 100644 --- a/src/auth/src/routes/statusOtp.ts +++ b/src/auth/src/routes/statusOtp.ts @@ -1,30 +1,28 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" -import { Otp } from "@shared/auth"; +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; +import { Otp } from '@shared/auth'; export const StatusOtpRes = Type.Union([ - typeResponse("success", "statusOtp.success.enabled", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }), - typeResponse("success", "statusOtp.success.disabled"), - typeResponse("failure", "statusOtp.failure.generic") + typeResponse('success', 'statusOtp.success.enabled', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), + typeResponse('success', 'statusOtp.success.disabled'), + typeResponse('failure', 'statusOtp.failure.generic'), ]); export type StatusOtpRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.get( - "/api/auth/statusOtp", - { schema: { response: { "2xx": StatusOtpRes } }, config: { requireAuth: true } }, + '/api/auth/statusOtp', + { schema: { response: { '2xx': StatusOtpRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "statusOtp.failure.generic"); - let otpSecret = this.db.getUserOtpSecret(req.authUser.id); - if (isNullish(otpSecret)) - return makeResponse("success", "statusOtp.success.disabled"); - let otp = new Otp({ secret: otpSecret }) - return makeResponse("success", "statusOtp.success.enabled", { url: otp.totpURL }); + if (isNullish(req.authUser)) {return makeResponse('failure', 'statusOtp.failure.generic');} + const otpSecret = this.db.getUserOtpSecret(req.authUser.id); + if (isNullish(otpSecret)) {return makeResponse('success', 'statusOtp.success.disabled');} + const otp = new Otp({ secret: otpSecret }); + return makeResponse('success', 'statusOtp.success.enabled', { url: otp.totpURL }); }, ); }; diff --git a/src/auth/src/routes/whoami.ts b/src/auth/src/routes/whoami.ts index 5f88cc1..f7b1676 100644 --- a/src/auth/src/routes/whoami.ts +++ b/src/auth/src/routes/whoami.ts @@ -1,24 +1,23 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "whoami.success", { name: Type.String() }), - typeResponse("failure", "whoami.failure.generic") + typeResponse('success', 'whoami.success', { name: Type.String() }), + typeResponse('failure', 'whoami.failure.generic'), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { fastify.get( - "/api/auth/whoami", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/whoami', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "whoami.failure.generic") - return makeResponse("success", "whoami.success", { name: req.authUser.name }) + if (isNullish(req.authUser)) {return makeResponse('failure', 'whoami.failure.generic');} + return makeResponse('success', 'whoami.success', { name: req.authUser.name }); }, ); }; diff --git a/src/auth/src/run.ts b/src/auth/src/run.ts index efaa386..9f9e3dd 100644 --- a/src/auth/src/run.ts +++ b/src/auth/src/run.ts @@ -1,7 +1,7 @@ // this sould only be used by the docker file ! -import fastify, { FastifyInstance } from "fastify"; -import app from "./app" +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; const start = async () => { const envToLogger = { @@ -16,15 +16,16 @@ const start = async () => { }, production: true, test: false, - } + }; const f: FastifyInstance = fastify({ logger: envToLogger.development }); try { await f.register(app, {}); - await f.listen({ port: 80, host: '0.0.0.0' }) - } catch (err) { - f.log.error(err) - process.exit(1) + await f.listen({ port: 80, host: '0.0.0.0' }); } -} -start() + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/auth/vite.config.js b/src/auth/vite.config.js index 89a197c..1a7883e 100644 --- a/src/auth/vite.config.js +++ b/src/auth/vite.config.js @@ -1,8 +1,8 @@ -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' +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(); @@ -20,7 +20,7 @@ function collectDeps(...pkgJsonPaths) { const externals = collectDeps( './package.json', - '../@shared/package.json' + '../@shared/package.json', ); @@ -42,5 +42,5 @@ export default defineConfig({ target: 'node22', // or whatever Node version you use sourcemap: true, minify: false, // for easier debugging - } -}) + }, +}); From bdcadcf3244f23ec7ffdd198e80e8edc87188656 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 19:04:24 +0200 Subject: [PATCH 04/32] style(src/icons): auto-correction of the linter - using pnpm eslint --fix ./src --- src/icons/src/app.ts | 34 ++++++++++++------------- src/icons/src/plugins/sensible.ts | 8 +++--- src/icons/src/routes/set.ts | 41 ++++++++++++++++--------------- src/icons/src/run.ts | 19 +++++++------- src/icons/vite.config.js | 16 ++++++------ 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts index d2b29df..47ca618 100644 --- a/src/icons/src/app.ts +++ b/src/icons/src/app.ts @@ -1,9 +1,9 @@ -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 { 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'; // @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); @@ -20,7 +20,7 @@ declare module 'fastify' { const app: FastifyPluginAsync = async ( fastify, - opts + opts, ): Promise => { // Place here your custom code! for (const plugin of Object.values(plugins)) { @@ -30,20 +30,20 @@ const app: FastifyPluginAsync = async ( void fastify.register(route as any, {}); } - await fastify.register(db.useDatabase as any, {}) - void fastify.register(fastifyFormBody, {}) - void fastify.register(fastifyMultipart, {}) + await fastify.register(db.useDatabase as any, {}); + void fastify.register(fastifyFormBody, {}); + void fastify.register(fastifyMultipart, {}); console.log(fastify.db.getUser(1)); // The use of fastify-plugin is required to be able // to export the decorators to the outer scope void fastify.register(fp(async (fastify) => { - const image_store = process.env.USER_ICONS_STORE ?? "/tmp/icons"; - fastify.decorate('image_store', image_store) - await mkdir(fastify.image_store, { recursive: true }) - })) + const image_store = process.env.USER_ICONS_STORE ?? '/tmp/icons'; + fastify.decorate('image_store', image_store); + await mkdir(fastify.image_store, { recursive: true }); + })); -} +}; -export default app -export { app } +export default app; +export { app }; diff --git a/src/icons/src/plugins/sensible.ts b/src/icons/src/plugins/sensible.ts index 5be2c8a..8c2093c 100644 --- a/src/icons/src/plugins/sensible.ts +++ b/src/icons/src/plugins/sensible.ts @@ -1,5 +1,5 @@ -import fp from 'fastify-plugin' -import sensible, { FastifySensibleOptions } from '@fastify/sensible' +import fp from 'fastify-plugin'; +import sensible, { FastifySensibleOptions } from '@fastify/sensible'; /** * This plugins adds some utilities to handle http errors @@ -7,5 +7,5 @@ import sensible, { FastifySensibleOptions } from '@fastify/sensible' * @see https://github.com/fastify/fastify-sensible */ export default fp(async (fastify) => { - fastify.register(sensible) -}) + fastify.register(sensible); +}); diff --git a/src/icons/src/routes/set.ts b/src/icons/src/routes/set.ts index 7e854c3..6cf1e00 100644 --- a/src/icons/src/routes/set.ts +++ b/src/icons/src/routes/set.ts @@ -1,9 +1,9 @@ -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' +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 => { // await fastify.register(authMethod, {}); @@ -13,37 +13,38 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise => { // it sets some configuration options, and set the actual function that will handle the request fastify.addContentTypeParser('*', function(request, payload, done: any) { - done() + done(); }); fastify.post('/:userid', async function(request, reply) { - let buffer = await rawBody(request.raw); + const buffer = await rawBody(request.raw); // this is how we get the `:userid` part of things const userid: string | undefined = (request.params as any)['userid']; if (isNullish(userid)) { return await reply.code(403); } - const image_store: string = fastify.getDecorator('image_store') - const image_path = join(image_store, userid) + const image_store: string = fastify.getDecorator('image_store'); + const image_path = join(image_store, userid); try { - let img = sharp(buffer); + const img = sharp(buffer); img.resize({ height: 128, width: 128, fit: 'fill', - }) - const data = await img.png({ compressionLevel: 6 }).toBuffer() - let image_file = await open(image_path, "w", 0o666) + }); + 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: any) { + await image_file.close(); + } + catch (e: any) { fastify.log.error(`Error: ${e}`); reply.code(400); - return { status: "error", message: e.toString() }; + return { status: 'error', message: e.toString() }; } - }) -} + }); +}; -export default route +export default route; diff --git a/src/icons/src/run.ts b/src/icons/src/run.ts index efaa386..9f9e3dd 100644 --- a/src/icons/src/run.ts +++ b/src/icons/src/run.ts @@ -1,7 +1,7 @@ // this sould only be used by the docker file ! -import fastify, { FastifyInstance } from "fastify"; -import app from "./app" +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; const start = async () => { const envToLogger = { @@ -16,15 +16,16 @@ const start = async () => { }, production: true, test: false, - } + }; const f: FastifyInstance = fastify({ logger: envToLogger.development }); try { await f.register(app, {}); - await f.listen({ port: 80, host: '0.0.0.0' }) - } catch (err) { - f.log.error(err) - process.exit(1) + await f.listen({ port: 80, host: '0.0.0.0' }); } -} -start() + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/icons/vite.config.js b/src/icons/vite.config.js index 8de6f14..9aba98c 100644 --- a/src/icons/vite.config.js +++ b/src/icons/vite.config.js @@ -1,8 +1,8 @@ -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' +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(); @@ -20,7 +20,7 @@ function collectDeps(...pkgJsonPaths) { const externals = collectDeps( './package.json', - '../@shared/package.json' + '../@shared/package.json', ); @@ -42,5 +42,5 @@ export default defineConfig({ target: 'node22', // or whatever Node version you use sourcemap: false, minify: true, // for easier debugging - } -}) + }, +}); From 35665138811438c2e672b75c8d99ab832c229366 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 21:48:23 +0200 Subject: [PATCH 05/32] core(package): removing the double package and added it in the src folder The package for the core of the project is in src/ --- package.json | 9 -------- src/eslint.config.js | 55 ++++++++++++++++++++++++++++++++++++++++++++ src/package.json | 7 +++++- 3 files changed, 61 insertions(+), 10 deletions(-) delete mode 100644 package.json create mode 100644 src/eslint.config.js diff --git a/package.json b/package.json deleted file mode 100644 index f995973..0000000 --- a/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "devDependencies": { - "@eslint/js": "^9.36.0", - "@typescript-eslint/eslint-plugin": "^8.44.1", - "@typescript-eslint/parser": "^8.44.1", - "eslint": "^9.36.0", - "typescript-eslint": "^8.44.1" - } -} diff --git a/src/eslint.config.js b/src/eslint.config.js new file mode 100644 index 0000000..5b8cab6 --- /dev/null +++ b/src/eslint.config.js @@ -0,0 +1,55 @@ +import js from '@eslint/js'; +import ts from 'typescript-eslint'; + +export default [ + js.configs.recommended, + ...ts.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + }, + rules: { + 'arrow-spacing': ['warn', { before: true, after: true }], + 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': 'error', + 'comma-style': 'error', + curly: ['error', 'multi-line', 'consistent'], + 'dot-location': ['error', 'property'], + 'handle-callback-err': 'off', + indent: ['error', 'tab'], + 'keyword-spacing': 'error', + 'max-nested-callbacks': ['error', { max: 4 }], + 'max-statements-per-line': ['error', { max: 2 }], + 'no-console': 'off', + 'no-empty-function': 'error', + 'no-floating-decimal': 'error', + 'no-inline-comments': 'error', + 'no-lonely-if': 'error', + 'no-multi-spaces': 'error', + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }], + 'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }], + 'no-trailing-spaces': ['error'], + 'no-var': 'error', + 'no-undef': 'off', + 'object-curly-spacing': ['error', 'always'], + 'prefer-const': 'error', + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'space-before-blocks': 'error', + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], + 'space-in-parens': 'error', + 'space-infix-ops': 'error', + 'space-unary-ops': 'error', + 'spaced-comment': 'error', + yoda: 'error', + }, + }, +]; diff --git a/src/package.json b/src/package.json index 8e8f7ea..ceab98f 100644 --- a/src/package.json +++ b/src/package.json @@ -14,7 +14,12 @@ "install-all": "npm install" }, "devDependencies": { - "rimraf": "^5.0.1" + "rimraf": "^5.0.1", + "@eslint/js": "^9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^9.36.0", + "typescript-eslint": "^8.44.1" }, "dependencies": { "bindings": "^1.5.0" From 0a9c7002285775660d1bbea5a878ae595a7ec0b3 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:09:23 +0200 Subject: [PATCH 06/32] ci(lint): adding a github action to check if lint is respected - The github action will check: - If the code can be build - If the code respect the linter --- .github/workflows/lint.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..e62122b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,37 @@ +name: Build & Linter + +on: + push: + branches: + - "**" + pull_request: + branches: + - "**" + +permissions: + contents: read + +jobs: + name: Build & Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + run_install: false + - name: Install dependencies with pnpm + run: pnpm --prefix=./src/ install --frozen-lockfile + - name: Check linting + run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 + - name: Build + run: pnpm --prefix=./src/ run build + From a47ddb49bddf28b7653983790086539b362b38de Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:26:53 +0200 Subject: [PATCH 07/32] style(eslint): removing the files of the old version --- eslint.config.js | 55 ------------------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 eslint.config.js diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 5b8cab6..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,55 +0,0 @@ -import js from '@eslint/js'; -import ts from 'typescript-eslint'; - -export default [ - js.configs.recommended, - ...ts.configs.recommended, - { - languageOptions: { - ecmaVersion: 'latest', - }, - rules: { - 'arrow-spacing': ['warn', { before: true, after: true }], - 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], - 'comma-dangle': ['error', 'always-multiline'], - 'comma-spacing': 'error', - 'comma-style': 'error', - curly: ['error', 'multi-line', 'consistent'], - 'dot-location': ['error', 'property'], - 'handle-callback-err': 'off', - indent: ['error', 'tab'], - 'keyword-spacing': 'error', - 'max-nested-callbacks': ['error', { max: 4 }], - 'max-statements-per-line': ['error', { max: 2 }], - 'no-console': 'off', - 'no-empty-function': 'error', - 'no-floating-decimal': 'error', - 'no-inline-comments': 'error', - 'no-lonely-if': 'error', - 'no-multi-spaces': 'error', - 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }], - 'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }], - 'no-trailing-spaces': ['error'], - 'no-var': 'error', - 'no-undef': 'off', - 'object-curly-spacing': ['error', 'always'], - 'prefer-const': 'error', - quotes: ['error', 'single'], - semi: ['error', 'always'], - 'space-before-blocks': 'error', - 'space-before-function-paren': [ - 'error', - { - anonymous: 'never', - named: 'never', - asyncArrow: 'always', - }, - ], - 'space-in-parens': 'error', - 'space-infix-ops': 'error', - 'space-unary-ops': 'error', - 'spaced-comment': 'error', - yoda: 'error', - }, - }, -]; From c88cc67e0b622722cb344dfe144c88f632e20cf1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:27:27 +0200 Subject: [PATCH 08/32] style(icons/vite): removing the comment on the same line - Moving the comment below --- src/icons/vite.config.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/icons/vite.config.js b/src/icons/vite.config.js index 9aba98c..2c172a5 100644 --- a/src/icons/vite.config.js +++ b/src/icons/vite.config.js @@ -25,22 +25,27 @@ const externals = collectDeps( export default defineConfig({ - root: __dirname, // service root + 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 + 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 + target: 'node22', + // or whatever Node version you use sourcemap: false, - minify: true, // for easier debugging + minify: true, + // for easier debugging }, }); From d5e7f57ef20b4c394289c97c8e09d2a521da39e3 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:28:08 +0200 Subject: [PATCH 09/32] style(auth/vite): removing the comment on the same line - Moving the comment below --- src/auth/vite.config.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/auth/vite.config.js b/src/auth/vite.config.js index 1a7883e..aeb8fd8 100644 --- a/src/auth/vite.config.js +++ b/src/auth/vite.config.js @@ -25,22 +25,27 @@ const externals = collectDeps( export default defineConfig({ - root: __dirname, // service root + 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 + 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 + target: 'node22', + // or whatever Node version you use sourcemap: true, - minify: false, // for easier debugging + minify: false, + // for easier debugging }, }); From 6f42fe6929ea0c08aed3f2e3218bc8818c473721 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:31:28 +0200 Subject: [PATCH 10/32] fix!(icons/app): removing the global ignore - Changing the ts-ignore by ts-execpt-error because safer and more specific --- src/icons/src/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts index 47ca618..1d4bb30 100644 --- a/src/icons/src/app.ts +++ b/src/icons/src/app.ts @@ -5,9 +5,9 @@ import { mkdir } from 'node:fs/promises'; import fp from 'fastify-plugin'; import * as db from '@shared/database'; -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-except-error: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-except-error: import.meta.glob is a vite thing. Typescript doesn't know this... const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); From 8e34b0563a79539d782be621d0c21c3748bab0d4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:32:49 +0200 Subject: [PATCH 11/32] fix(icons/app): changing the variable name for one not already declared - The name is now fastify2 because fastify was already declared --- src/icons/src/app.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts index 1d4bb30..aeb9498 100644 --- a/src/icons/src/app.ts +++ b/src/icons/src/app.ts @@ -37,10 +37,10 @@ const app: FastifyPluginAsync = async ( // The use of fastify-plugin is required to be able // to export the decorators to the outer scope - void fastify.register(fp(async (fastify) => { + void fastify.register(fp(async (fastify2) => { const image_store = process.env.USER_ICONS_STORE ?? '/tmp/icons'; - fastify.decorate('image_store', image_store); - await mkdir(fastify.image_store, { recursive: true }); + fastify2.decorate('image_store', image_store); + await mkdir(fastify2.image_store, { recursive: true }); })); }; From 369d0b2407a54947b02df473ccee35c60c319f99 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:50:27 +0200 Subject: [PATCH 12/32] fix(ci/lint): indentation was wrong - correcting the indentation (yml de mort) --- .github/workflows/lint.yml | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e62122b..86e7d36 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,26 +12,26 @@ permissions: contents: read jobs: - name: Build & Lint - runs-on: ubuntu-latest + name: Build & Lint + runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Setup pnpm - uses: pnpm/action-setup@v3 - with: - version: 8 - run_install: false - - name: Install dependencies with pnpm - run: pnpm --prefix=./src/ install --frozen-lockfile - - name: Check linting - run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 - - name: Build - run: pnpm --prefix=./src/ run build + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + run_install: false + - name: Install dependencies with pnpm + run: pnpm --prefix=./src/ install --frozen-lockfile + - name: Check linting + run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 + - name: Build + run: pnpm --prefix=./src/ run build From 61da54528165743bab74fc6be0db7399fe89708a Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:53:24 +0200 Subject: [PATCH 13/32] fix(ci/lint): indentation was wrong - correcting the indentation (yml de mort) --- .github/workflows/lint.yml | 43 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 86e7d36..eeb0343 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,26 +12,27 @@ permissions: contents: read jobs: - name: Build & Lint - runs-on: ubuntu-latest + build: + name: Build & Typecheck + runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Setup pnpm - uses: pnpm/action-setup@v3 - with: - version: 8 - run_install: false - - name: Install dependencies with pnpm - run: pnpm --prefix=./src/ install --frozen-lockfile - - name: Check linting - run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 - - name: Build - run: pnpm --prefix=./src/ run build + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + run_install: false + - name: Install dependencies with pnpm + run: pnpm --prefix=./src/ install --frozen-lockfile + - name: Check linting + run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 + - name: Build + run: pnpm --prefix=./src/ run build From ecb77f98d485c04c4f21b83ae8be7324dd604998 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:57:22 +0200 Subject: [PATCH 14/32] fix(ci/lint): adding the correct version on node --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index eeb0343..3415c8c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: 'pnpm' - name: Setup pnpm uses: pnpm/action-setup@v3 From 3617ce6361f26c9ae07f667e4dfe7e0354883328 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 22:57:49 +0200 Subject: [PATCH 15/32] fix(ci/lint): adding the correct version on pnpm - Using the same version of the flake (please i use nix btw) --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3415c8c..445de7f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v3 with: - version: 8 + version: 10 run_install: false - name: Install dependencies with pnpm run: pnpm --prefix=./src/ install --frozen-lockfile From 9a025f16f8fb97bd8cbcbdf797c3992061e95615 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 23:01:21 +0200 Subject: [PATCH 16/32] fix(ci/lint): adding the correct version of the action pnpm --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 445de7f..2b10307 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,7 @@ jobs: node-version: '22' cache: 'pnpm' - name: Setup pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v4 with: version: 10 run_install: false From 57d2f4e942f870c03725fcbf414188016509169d Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 23:03:44 +0200 Subject: [PATCH 17/32] fix(ci/lint): testing without node cache --- .github/workflows/lint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2b10307..e10e8dd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,7 +23,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' - cache: 'pnpm' - name: Setup pnpm uses: pnpm/action-setup@v4 with: From bf34d113e035542eb6c0b480c8d76e4e89ca3cfd Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 23:07:23 +0200 Subject: [PATCH 18/32] fix(ci/lint): test w/ cd ./src before pnpm executions --- .github/workflows/lint.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e10e8dd..7ec9319 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,10 +28,12 @@ jobs: with: version: 10 run_install: false + - name: Directory src + run: cd ./src - name: Install dependencies with pnpm - run: pnpm --prefix=./src/ install --frozen-lockfile + run: pnpm install --frozen-lockfile - name: Check linting - run: pnpm --prefix=./src/ exec eslint . --max-warnings=0 + run: pnpm exec eslint . --max-warnings=0 - name: Build - run: pnpm --prefix=./src/ run build + run: pnpm run build From 782d453cc879b3d6162ffb2b71b21caa055544bf Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 23:10:23 +0200 Subject: [PATCH 19/32] fix(ci/lint): Using the working directory - Learning the working-directory keyword in gh action --- .github/workflows/lint.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7ec9319..067854f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,12 +28,13 @@ jobs: with: version: 10 run_install: false - - name: Directory src - run: cd ./src - name: Install dependencies with pnpm + working-directory: ./src run: pnpm install --frozen-lockfile - name: Check linting + working-directory: ./src run: pnpm exec eslint . --max-warnings=0 - name: Build + working-directory: ./src run: pnpm run build From d11e4a4516aaaecc19850272181a24f67d5602ab Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Sep 2025 23:11:42 +0200 Subject: [PATCH 20/32] fix(ci/lint): Removing the frozen lockfile --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 067854f..c1040fe 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: run_install: false - name: Install dependencies with pnpm working-directory: ./src - run: pnpm install --frozen-lockfile + run: pnpm install - name: Check linting working-directory: ./src run: pnpm exec eslint . --max-warnings=0 From 404735fe229c4c8e2ef7329a9efa1845b18c9f16 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Mon, 29 Sep 2025 11:50:53 +0200 Subject: [PATCH 21/32] fixes(eslint): fixing everything that eslint complained about --- src/@shared/src/auth/index.ts | 17 +- src/@shared/src/database/index.ts | 9 +- src/@shared/src/database/mixin/_base.ts | 2 +- src/@shared/src/database/mixin/user.ts | 20 +- src/@shared/src/utils/index.ts | 8 +- src/auth/src/app.ts | 23 +- src/auth/src/routes/disableOtp.ts | 2 + src/auth/src/routes/enableOtp.ts | 2 + src/auth/src/routes/login.ts | 2 + src/auth/src/routes/logout.ts | 2 + src/auth/src/routes/otp.ts | 34 +- src/auth/src/routes/signin.ts | 8 +- src/auth/src/routes/statusOtp.ts | 2 + src/auth/src/routes/whoami.ts | 2 + src/icons/src/app.ts | 18 +- src/icons/src/routes/set.ts | 15 +- src/package-lock.json | 1469 ++++++++++++++++++++++- 17 files changed, 1566 insertions(+), 69 deletions(-) diff --git a/src/@shared/src/auth/index.ts b/src/@shared/src/auth/index.ts index b814771..1a25d67 100644 --- a/src/@shared/src/auth/index.ts +++ b/src/@shared/src/auth/index.ts @@ -26,11 +26,16 @@ declare module 'fastify' { export interface FastifyContextConfig { requireAuth?: boolean; } + export interface RouteOptions { + [kRouteAuthDone]: boolean; + } } export const Otp = OTP; let jwtAdded = false; export const jwtPlugin = fp(async (fastify, _opts) => { + void _opts; + if (jwtAdded) return; jwtAdded = true; const env = process.env.JWT_SECRET; @@ -65,16 +70,18 @@ export type JwtType = Static; let authAdded = false; export const authPlugin = fp(async (fastify, _opts) => { + void _opts; + if (authAdded) return void console.log('skipping'); authAdded = true; - await fastify.register(useDatabase as any, {}); - await fastify.register(jwtPlugin as any, {}); + await fastify.register(useDatabase as FastifyPluginAsync, {}); + await fastify.register(jwtPlugin as FastifyPluginAsync, {}); await fastify.register(cookie); - if (!fastify.hasRequestDecorator('authUser')) {fastify.decorateRequest('authUser', undefined);} + if (!fastify.hasRequestDecorator('authUser')) { fastify.decorateRequest('authUser', undefined); } fastify.addHook('onRoute', (routeOpts) => { if ( routeOpts.config?.requireAuth && - !(routeOpts as any)[kRouteAuthDone] + !routeOpts[kRouteAuthDone] ) { const f: preValidationAsyncHookHandler = async function(req, res) { try { @@ -119,7 +126,7 @@ export const authPlugin = fp(async (fastify, _opts) => { routeOpts.preValidation = [routeOpts.preValidation, f]; } - (routeOpts as any)[kRouteAuthDone] = true; + routeOpts[kRouteAuthDone] = true; } }); }); diff --git a/src/@shared/src/database/index.ts b/src/@shared/src/database/index.ts index d3bd9cf..1f6d237 100644 --- a/src/@shared/src/database/index.ts +++ b/src/@shared/src/database/index.ts @@ -21,14 +21,15 @@ let dbAdded = false; export const useDatabase = fp(async function( f: FastifyInstance, - _options: {}) { - if (dbAdded) {return;} + _options: object) { + void _options; + if (dbAdded) { return; } dbAdded = true; const path = process.env.DATABASE_DIR; - if (isNullish(path)) {throw 'env `DATABASE_DIR` not defined';} + if (isNullish(path)) { throw 'env `DATABASE_DIR` not defined'; } f.log.info(`Opening database with path: ${path}/database.db`); const db: Database = new DbImpl(`${path}/database.db`) as Database; - if (!f.hasDecorator('db')) {f.decorate('db', db);} + if (!f.hasDecorator('db')) { f.decorate('db', db); } }); export default useDatabase; diff --git a/src/@shared/src/database/mixin/_base.ts b/src/@shared/src/database/mixin/_base.ts index e039883..5811c9e 100644 --- a/src/@shared/src/database/mixin/_base.ts +++ b/src/@shared/src/database/mixin/_base.ts @@ -1,6 +1,6 @@ import sqlite from 'better-sqlite3'; -// @ts-ignore: this file is included using vite, typescript doesn't know how to include this... +// @ts-expect-error: this file is included using vite, typescript doesn't know how to include this... import initSql from '../init.sql?raw'; export type SqliteReturn = object | undefined; diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index 6885100..d33aac8 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -94,17 +94,17 @@ export const UserImpl: Omit = { }, getUserOtpSecret(this: IUserDb, id: UserId): string | undefined { - const otp: any = this.prepare('SELECT otp FROM user WHERE id = @id LIMIT 1').get({ id }) as SqliteReturn; + const otp = this.prepare('SELECT otp FROM user WHERE id = @id LIMIT 1').get({ id }) as ({ otp: string } | null | undefined); if (isNullish(otp?.otp)) return undefined; return otp.otp; }, ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined { const otp = this.getUserOtpSecret(id); - if (!isNullish(otp)) {return otp;} + if (!isNullish(otp)) { return otp; } const otpGen = new Otp(); - const res: any = this.prepare('UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp') - .get({ id, otp: otpGen.secret }); + const res = this.prepare('UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp') + .get({ id, otp: otpGen.secret }) as ({ otp: string } | null | undefined); return res?.otp; }, @@ -154,12 +154,14 @@ async function hashPassword( * * @returns The user if it exists, undefined otherwise */ -function userFromRow(row: any): User | undefined { +function userFromRow(row: Partial): User | undefined { if (isNullish(row)) return undefined; + if (isNullish(row.id)) return undefined; + if (isNullish(row.name)) return undefined; return { - id: row.id as UserId, - name: row.name || undefined, - password: row.password || undefined, - otp: row.otp || undefined, + id: row.id, + name: row.name, + password: row.password ?? undefined, + otp: row.otp ?? undefined, }; } diff --git a/src/@shared/src/utils/index.ts b/src/@shared/src/utils/index.ts index 3918b27..56af348 100644 --- a/src/@shared/src/utils/index.ts +++ b/src/@shared/src/utils/index.ts @@ -1,4 +1,4 @@ -import { Type } from '@sinclair/typebox'; +import { TObject, TProperties, Type } from '@sinclair/typebox'; /** * @description Represent a message key @@ -12,7 +12,7 @@ import { Type } from '@sinclair/typebox'; * @example `pong.you.lost` */ export type MessageKey = string; -export type ResponseBase = { +export type ResponseBase = { kind: string, msg: MessageKey, payload?: T, @@ -26,7 +26,7 @@ export type ResponseBase = { * @example makeResponse("failure", "login.failure.invalid") * @example makeResponse("success", "login.success", { token: "supersecrettoken" }) */ -export function makeResponse(kind: string, key: MessageKey, payload?: T): ResponseBase { +export function makeResponse(kind: string, key: MessageKey, payload?: T): ResponseBase { console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`); return { kind, msg: key, payload }; } @@ -39,7 +39,7 @@ export function makeResponse(kind: string, key: MessageKey, payload?: T) * @example typeResponse("otpRequired", "login.otpRequired", { token: Type.String() }) * @example typeResponse("success", "login.success", { token: Type.String() }) */ -export function typeResponse(kind: string, key: MessageKey | MessageKey[], payload?: any): any { +export function typeResponse(kind: string, key: MessageKey | MessageKey[], payload?: TProperties): TObject { let tKey; if (key instanceof Array) { tKey = Type.Union(key.map(l => Type.Const(l))); diff --git a/src/auth/src/app.ts b/src/auth/src/app.ts index 077e5c5..e9156e7 100644 --- a/src/auth/src/app.ts +++ b/src/auth/src/app.ts @@ -1,17 +1,14 @@ 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 auth from '@shared/auth'; -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @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-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @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 { @@ -19,20 +16,18 @@ declare module 'fastify' { } } -const app: FastifyPluginAsync = async ( - fastify, - opts, -): Promise => { - await fastify.register(db.useDatabase as any, {}); - await fastify.register(auth.jwtPlugin as any, {}); - await fastify.register(auth.authPlugin as any, {}); +const app: FastifyPluginAsync = async (fastify, opts): Promise => { + void opts; + await fastify.register(db.useDatabase as FastifyPluginAsync, {}); + await fastify.register(auth.jwtPlugin as FastifyPluginAsync, {}); + await fastify.register(auth.authPlugin as FastifyPluginAsync, {}); // Place here your custom code! for (const plugin of Object.values(plugins)) { - void fastify.register(plugin as any, {}); + void fastify.register(plugin as FastifyPluginAsync, {}); } for (const route of Object.values(routes)) { - void fastify.register(route as any, {}); + void fastify.register(route as FastifyPluginAsync, {}); } void fastify.register(fastifyFormBody, {}); diff --git a/src/auth/src/routes/disableOtp.ts b/src/auth/src/routes/disableOtp.ts index 38dd0c8..7b9b9ef 100644 --- a/src/auth/src/routes/disableOtp.ts +++ b/src/auth/src/routes/disableOtp.ts @@ -12,10 +12,12 @@ export const WhoAmIRes = Type.Union([ export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.put( '/api/auth/disableOtp', { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { + void _res; if (isNullish(req.authUser)) {return makeResponse('failure', 'disableOtp.failure.generic');} this.db.deleteUserOtpSecret(req.authUser.id); return makeResponse('success', 'disableOtp.success'); diff --git a/src/auth/src/routes/enableOtp.ts b/src/auth/src/routes/enableOtp.ts index ec25acb..dc83da2 100644 --- a/src/auth/src/routes/enableOtp.ts +++ b/src/auth/src/routes/enableOtp.ts @@ -13,10 +13,12 @@ export const WhoAmIRes = Type.Union([ export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.put( '/api/auth/enableOtp', { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { + void _res; if (isNullish(req.authUser)) {return makeResponse('failure', 'enableOtp.failure.noUser');} const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); if (isNullish(otpSecret)) {return makeResponse('failure', 'enableOtp.failure.noSecret');} diff --git a/src/auth/src/routes/login.ts b/src/auth/src/routes/login.ts index b61d6ab..10121a9 100644 --- a/src/auth/src/routes/login.ts +++ b/src/auth/src/routes/login.ts @@ -21,10 +21,12 @@ export const LoginRes = Type.Union([ export type LoginRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: LoginReq; Response: LoginRes }>( '/api/auth/login', { schema: { body: LoginReq, response: { '2xx': LoginRes } } }, async function(req, _res) { + void _res; try { const { name, password } = req.body; const user = this.db.getUserFromName(name); diff --git a/src/auth/src/routes/logout.ts b/src/auth/src/routes/logout.ts index 5e59c6e..f2fce67 100644 --- a/src/auth/src/routes/logout.ts +++ b/src/auth/src/routes/logout.ts @@ -1,9 +1,11 @@ import { FastifyPluginAsync } from 'fastify'; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post( '/api/auth/logout', async function(_req, res) { + void _req; return res.clearCookie('token').send('{}'); }, ); diff --git a/src/auth/src/routes/otp.ts b/src/auth/src/routes/otp.ts index 5d49b3f..d555eeb 100644 --- a/src/auth/src/routes/otp.ts +++ b/src/auth/src/routes/otp.ts @@ -21,30 +21,35 @@ type OtpRes = Static; const OTP_TOKEN_TIMEOUT_SEC = 120; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: OtpReq }>( '/api/auth/otp', { schema: { body: OtpReq, response: { '2xx': OtpRes } } }, async function(req, _res) { + void _res; try { const { token, code } = req.body; // lets try to decode+verify the jwt const dJwt = this.jwt.verify(token); // is the jwt a valid `otp` jwt ? - if (dJwt.kind != 'otp') - // no ? fuck off then - {return makeResponse('failed', 'otp.failed.invalid');} + if (dJwt.kind != 'otp') { + // no ? fuck off then + return makeResponse('failed', 'otp.failed.invalid'); + } // is it too old ? - if (dJwt.createdAt + OTP_TOKEN_TIMEOUT_SEC * 1000 < Date.now()) - // yes ? fuck off then, redo the password - {return makeResponse('failed', 'otp.failed.timeout');} + if (dJwt.createdAt + OTP_TOKEN_TIMEOUT_SEC * 1000 < Date.now()) { + // yes ? fuck off then, redo the password + return makeResponse('failed', 'otp.failed.timeout'); + } // get the Otp sercret from the db const user = this.db.getUserFromName(dJwt.who); - if (isNullish(user?.otp)) - // oops, either no user, or user without otpSecret - // fuck off - {return makeResponse('failed', 'otp.failed.noSecret');} + if (isNullish(user?.otp)) { + // oops, either no user, or user without otpSecret + // fuck off + return makeResponse('failed', 'otp.failed.noSecret'); + } // good lets now verify the token you gave us is the correct one... const otpHandle = new Otp({ secret: user.otp }); @@ -58,10 +63,11 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { ]; // checking if any of the array match - if (tokens.some((c) => c === code)) - // they do ! - // gg you are now logged in ! - {return makeResponse('success', 'otp.success', { token: this.signJwt('auth', dJwt.who) });} + if (tokens.some((c) => c === code)) { + // they do ! + // gg you are now logged in ! + return makeResponse('success', 'otp.success', { token: this.signJwt('auth', dJwt.who) }); + } } catch { return makeResponse('failed', 'otp.failed.generic'); diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index 02cb9ba..b22ade6 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -3,7 +3,7 @@ import { FastifyPluginAsync } from 'fastify'; import { Static, Type } from '@sinclair/typebox'; import { typeResponse, makeResponse, isNullish } from '@shared/utils'; -const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/; +const USERNAME_CHECK: RegExp = /^[a-zA-Z_0-9]+$/; const SignInReq = Type.Object({ name: Type.String(), @@ -28,11 +28,13 @@ const SignInRes = Type.Union([ type SignInRes = Static; -const route: FastifyPluginAsync = async (fastify, opts): Promise => { +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: SignInReq }>( '/api/auth/signin', { schema: { body: SignInReq, response: { '200': SignInRes, '5xx': Type.Object({}) } } }, - async function(req, res) { + async function(req, _res) { + void _res; const { name, password } = req.body; if (name.length < 4) {return makeResponse('failed', 'signin.failed.username.tooshort');} diff --git a/src/auth/src/routes/statusOtp.ts b/src/auth/src/routes/statusOtp.ts index 2f71872..755f2c6 100644 --- a/src/auth/src/routes/statusOtp.ts +++ b/src/auth/src/routes/statusOtp.ts @@ -14,10 +14,12 @@ export const StatusOtpRes = Type.Union([ export type StatusOtpRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.get( '/api/auth/statusOtp', { schema: { response: { '2xx': StatusOtpRes } }, config: { requireAuth: true } }, async function(req, _res) { + void _res; if (isNullish(req.authUser)) {return makeResponse('failure', 'statusOtp.failure.generic');} const otpSecret = this.db.getUserOtpSecret(req.authUser.id); if (isNullish(otpSecret)) {return makeResponse('success', 'statusOtp.success.disabled');} diff --git a/src/auth/src/routes/whoami.ts b/src/auth/src/routes/whoami.ts index f7b1676..13232b6 100644 --- a/src/auth/src/routes/whoami.ts +++ b/src/auth/src/routes/whoami.ts @@ -12,10 +12,12 @@ export const WhoAmIRes = Type.Union([ export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.get( '/api/auth/whoami', { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { + void _res; if (isNullish(req.authUser)) {return makeResponse('failure', 'whoami.failure.generic');} return makeResponse('success', 'whoami.success', { name: req.authUser.name }); }, diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts index aeb9498..1ad0932 100644 --- a/src/icons/src/app.ts +++ b/src/icons/src/app.ts @@ -4,10 +4,11 @@ import fastifyMultipart from '@fastify/multipart'; import { mkdir } from 'node:fs/promises'; import fp from 'fastify-plugin'; import * as db from '@shared/database'; +import { authPlugin, jwtPlugin } from '@shared/auth'; -// @ts-except-error: import.meta.glob is a vite thing. Typescript doesn't know this... +// @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-except-error: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); @@ -20,20 +21,23 @@ declare module 'fastify' { const app: FastifyPluginAsync = async ( fastify, - opts, + _opts, ): Promise => { + void _opts; // Place here your custom code! for (const plugin of Object.values(plugins)) { - void fastify.register(plugin as any, {}); + void fastify.register(plugin as FastifyPluginAsync, {}); } for (const route of Object.values(routes)) { - void fastify.register(route as any, {}); + void fastify.register(route as FastifyPluginAsync, {}); } - await fastify.register(db.useDatabase as any, {}); + 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, {}); - console.log(fastify.db.getUser(1)); // The use of fastify-plugin is required to be able // to export the decorators to the outer scope diff --git a/src/icons/src/routes/set.ts b/src/icons/src/routes/set.ts index 6cf1e00..8063719 100644 --- a/src/icons/src/routes/set.ts +++ b/src/icons/src/routes/set.ts @@ -5,21 +5,22 @@ import sharp from 'sharp'; import rawBody from 'raw-body'; import { isNullish } from '@shared/utils'; -const route: FastifyPluginAsync = async (fastify, opts): Promise => { +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: any) { - done(); + fastify.addContentTypeParser('*', function(request, payload, done) { + done(null); }); - fastify.post('/:userid', async function(request, reply) { + 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 as any)['userid']; + const userid: string | undefined = (request.params)['userid']; if (isNullish(userid)) { return await reply.code(403); } @@ -38,10 +39,10 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise => { await image_file.write(data); await image_file.close(); } - catch (e: any) { + catch (e) { fastify.log.error(`Error: ${e}`); reply.code(400); - return { status: 'error', message: e.toString() }; + return { status: 'error' }; } }); }; diff --git a/src/package-lock.json b/src/package-lock.json index dd33633..88a1c01 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -16,7 +16,12 @@ "bindings": "^1.5.0" }, "devDependencies": { - "rimraf": "^5.0.1" + "@eslint/js": "^9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^9.36.0", + "rimraf": "^5.0.1", + "typescript-eslint": "^8.44.1" } }, "@shared": { @@ -537,6 +542,229 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@fastify/accept-negotiator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", @@ -966,6 +1194,58 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.3", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", @@ -1431,6 +1711,44 @@ "node": ">=8" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1760,6 +2078,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.17.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.0.tgz", @@ -1769,12 +2094,270 @@ "undici-types": "~6.21.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", + "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/type-utils": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", + "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", + "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.1", + "@typescript-eslint/types": "^8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", + "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", + "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", + "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", + "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", + "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.1", + "@typescript-eslint/tsconfig-utils": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", + "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", + "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -1832,6 +2415,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -1955,6 +2545,19 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1988,6 +2591,16 @@ "node": ">= 0.8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2108,6 +2721,13 @@ "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2203,6 +2823,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2320,6 +2947,333 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2347,6 +3301,43 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stringify": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", @@ -2386,6 +3377,13 @@ "node": ">=20" } }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", @@ -2564,12 +3562,38 @@ } } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-my-way": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", @@ -2596,6 +3620,27 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2684,6 +3729,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -2691,6 +3762,13 @@ "dev": true, "license": "MIT" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2758,6 +3836,53 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2800,6 +3925,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2809,6 +3944,29 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -2870,6 +4028,26 @@ "node": ">=10" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-ref-resolver": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", @@ -2895,6 +4073,37 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/light-my-request": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", @@ -2945,6 +4154,13 @@ "node": ">=6" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2970,6 +4186,43 @@ "node": ">= 0.6" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -3102,6 +4355,13 @@ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -3158,6 +4418,24 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/otp": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/otp/-/otp-1.1.2.tgz", @@ -3209,6 +4487,19 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -3417,6 +4708,16 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -3443,6 +4744,37 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -3646,6 +4978,30 @@ "rollup": "^4.0.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4125,6 +5481,19 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "license": "BSD-3-Clause" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toad-cache": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", @@ -4143,6 +5512,19 @@ "node": ">=0.6" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsconfck": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", @@ -4183,6 +5565,19 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4196,6 +5591,45 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz", + "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.44.1", + "@typescript-eslint/parser": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -4211,6 +5645,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4354,6 +5798,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4468,6 +5922,19 @@ "engines": { "node": ">=12" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } From 537cd03bb5462bb1500735cbd21fa8dc4a383722 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Mon, 29 Sep 2025 11:54:30 +0200 Subject: [PATCH 22/32] fixes(tsc): typing issue --- src/@shared/src/database/mixin/user.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index d33aac8..bb65edf 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -39,7 +39,7 @@ export const UserImpl: Omit = { return userFromRow( this.prepare( 'SELECT * FROM user WHERE name = @name LIMIT 1', - ).get({ name }), + ).get({ name }) as (Partial | undefined), ); }, @@ -54,7 +54,7 @@ export const UserImpl: Omit = { return userFromRow( this.prepare('SELECT * FROM user WHERE id = @id LIMIT 1').get({ id, - }) as SqliteReturn, + }) as (Partial | undefined), ); }, @@ -71,7 +71,7 @@ export const UserImpl: Omit = { return userFromRow( this.prepare( 'INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *', - ).get({ name, password }), + ).get({ name, password }) as (Partial | undefined), ); }, @@ -154,7 +154,7 @@ async function hashPassword( * * @returns The user if it exists, undefined otherwise */ -function userFromRow(row: Partial): User | undefined { +function userFromRow(row?: Partial): User | undefined { if (isNullish(row)) return undefined; if (isNullish(row.id)) return undefined; if (isNullish(row.name)) return undefined; From 481a99ac279e631d2d19e8607eb9a72779a5b88f Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 29 Sep 2025 12:08:21 +0200 Subject: [PATCH 23/32] core(pre-commit): adding a lint check before commit --- .husky/pre-commit | 2 ++ src/package.json | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..e506909 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +pnpm lint-staged diff --git a/src/package.json b/src/package.json index ceab98f..9431b0f 100644 --- a/src/package.json +++ b/src/package.json @@ -7,11 +7,17 @@ "./icons", "./auth" ], + "lint-staged": { + "*": [ + "eslint --fix" + ] + }, "scripts": { "build": "npm run build --workspaces --if-present", "fclean": "rimraf \"**/dist\"", "clean": "rimraf \"**/node_modules\"", - "install-all": "npm install" + "install-all": "npm install", + "prepare": "husky" }, "devDependencies": { "rimraf": "^5.0.1", @@ -19,6 +25,8 @@ "@typescript-eslint/eslint-plugin": "^8.44.1", "@typescript-eslint/parser": "^8.44.1", "eslint": "^9.36.0", + "lint-staged": "^16.1.5", + "husky": "^9.1.7", "typescript-eslint": "^8.44.1" }, "dependencies": { From 8ef9b323d03d50c4692a9918651b2adacd06e7da Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 29 Sep 2025 12:11:25 +0200 Subject: [PATCH 24/32] fix(husky): adding precommit lint verification --- src/.husky/pre-commit | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/.husky/pre-commit diff --git a/src/.husky/pre-commit b/src/.husky/pre-commit new file mode 100644 index 0000000..e506909 --- /dev/null +++ b/src/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +pnpm lint-staged From 9424bcf2132b4554191a41862454284f4f00f189 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 29 Sep 2025 14:30:45 +0200 Subject: [PATCH 25/32] fix(package/script): adding a dev script for husky --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 9431b0f..78ca66f 100644 --- a/src/package.json +++ b/src/package.json @@ -17,7 +17,7 @@ "fclean": "rimraf \"**/dist\"", "clean": "rimraf \"**/node_modules\"", "install-all": "npm install", - "prepare": "husky" + "dev:prepare": "husky" }, "devDependencies": { "rimraf": "^5.0.1", From 80f9c132037f5cab372321dee0084116311a0d32 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 30 Sep 2025 14:39:19 +0200 Subject: [PATCH 26/32] core(flake/tmux): adding tmux env for me - Using tmux for developpement propose so adding a cmd to addit (tmux-setup) --- flake.nix | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/flake.nix b/flake.nix index 0aa5758..16f7fb5 100644 --- a/flake.nix +++ b/flake.nix @@ -19,6 +19,24 @@ flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; + tmux-setup = pkgs.writeShellScriptBin "tmux-setup" '' + #!/usr/bin/env sh + SESSION="transandance" + DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd) + if ! tmux has-session -t $SESSION 2>/dev/null; then + tmux new-session -d -s $SESSION -c "$DIR" -n dev + tmux send-keys -t $SESSION:0 'vim' C-m + tmux split-window -h -p 30 -t $SESSION:0 -c "$DIR" + tmux send-keys -t $SESSION:0.1 'exec zsh' C-m + tmux split-window -v -p 30 -t $SESSION:0.1 -c "$DIR" + tmux send-keys -t $SESSION:0.2 'watch -n0.5 pnpm --prefix=./src/ eslint .' C-m + tmux new-window -t $SESSION:1 -n git -c "$DIR" + tmux send-keys -t $SESSION:1 'lazygit' C-m + fi + tmux select-window -t $SESSION:0 + tmux select-pane -t $SESSION:0.0 + tmux attach -t $SESSION + ''; in { devShell = pkgs.mkShellNoCC { packages = with pkgs; [ @@ -31,6 +49,7 @@ dbmlSQLite.packages.${system}.default sqlite-interactive clang + tmux-setup ]; shellHook = '' export PODMAN_COMPOSE_WARNING_LOGS="false"; From ec4eec13c8beb6ed0baffc7a35f4aad3f0775b04 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 30 Sep 2025 14:42:12 +0200 Subject: [PATCH 27/32] core(flake/tmux): correct the name of the project --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 16f7fb5..5b742bc 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ pkgs = nixpkgs.legacyPackages.${system}; tmux-setup = pkgs.writeShellScriptBin "tmux-setup" '' #!/usr/bin/env sh - SESSION="transandance" + SESSION="transcendance" DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd) if ! tmux has-session -t $SESSION 2>/dev/null; then tmux new-session -d -s $SESSION -c "$DIR" -n dev From fbc870f2409a3ad3c27d2e86e442ccc6ecc89dcd Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 30 Sep 2025 14:46:57 +0200 Subject: [PATCH 28/32] fix(package/eslint): adding the type module like asked by npmx eslint --- src/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/package.json b/src/package.json index 78ca66f..37b53cf 100644 --- a/src/package.json +++ b/src/package.json @@ -1,5 +1,6 @@ { "name": "workspace", + "type": "module", "version": "0.0.0", "private": true, "workspaces": [ From 8e1cfc0c4a4458ff879cdab4d503d39b497cba53 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 30 Sep 2025 14:47:48 +0200 Subject: [PATCH 29/32] fix(flake/tmux): switching from pnpm to npx to workspace work I don't know why but seems better --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 5b742bc..50be788 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,7 @@ tmux split-window -h -p 30 -t $SESSION:0 -c "$DIR" tmux send-keys -t $SESSION:0.1 'exec zsh' C-m tmux split-window -v -p 30 -t $SESSION:0.1 -c "$DIR" - tmux send-keys -t $SESSION:0.2 'watch -n0.5 pnpm --prefix=./src/ eslint .' C-m + tmux send-keys -t $SESSION:0.2 'watch -n0.5 npx --prefix=./src/ eslint .' C-m tmux new-window -t $SESSION:1 -n git -c "$DIR" tmux send-keys -t $SESSION:1 'lazygit' C-m fi From 357f1cc128c7033957cabb8429f2ac32a080a154 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 3 Oct 2025 13:17:32 +0200 Subject: [PATCH 30/32] core(husky): adding the prefix --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index e506909..9e21c98 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,2 @@ #!/usr/bin/env sh -pnpm lint-staged +pnpm --prefix=./src/ lint-staged From c1b083322959ccdfa7fb6c5680209279692fb1a7 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 3 Oct 2025 13:31:35 +0200 Subject: [PATCH 31/32] test --- src/auth/src/routes/logout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/auth/src/routes/logout.ts b/src/auth/src/routes/logout.ts index f2fce67..912e602 100644 --- a/src/auth/src/routes/logout.ts +++ b/src/auth/src/routes/logout.ts @@ -3,7 +3,6 @@ import { FastifyPluginAsync } from 'fastify'; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { void _opts; fastify.post( - '/api/auth/logout', async function(_req, res) { void _req; return res.clearCookie('token').send('{}'); From bb43622684f90be11f8a9e3f6b0187e31a71bc94 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 3 Oct 2025 13:31:54 +0200 Subject: [PATCH 32/32] test --- src/auth/src/routes/logout.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/auth/src/routes/logout.ts b/src/auth/src/routes/logout.ts index 912e602..f2fce67 100644 --- a/src/auth/src/routes/logout.ts +++ b/src/auth/src/routes/logout.ts @@ -3,6 +3,7 @@ import { FastifyPluginAsync } from 'fastify'; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { void _opts; fastify.post( + '/api/auth/logout', async function(_req, res) { void _req; return res.clearCookie('token').send('{}');