From 3cccc18e9a50a6f9c1000005e347f1cd3fdab017 Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Fri, 16 Jan 2026 13:34:51 +0100 Subject: [PATCH] icons started to work yeah --- .gitignore | 4 + Docker.mk | 3 +- docker-compose.yml | 36 ++- nginx/conf/locations/icons.conf | 9 + src/@dockerfiles/deps.Dockerfile | 1 + src/auth/config/default.png | Bin 0 -> 22473 bytes src/auth/src/routes/guestLogin.ts | 2 + src/auth/src/routes/oauth2/callback.ts | 4 + src/auth/src/routes/signin.ts | 2 + src/icons/.dockerignore | 2 + src/icons/package.json | 35 +++ src/icons/src/app.ts | 33 ++ src/icons/src/openapi.ts | 21 ++ src/icons/src/plugins/README.md | 16 + src/icons/src/routes/set.ts | 72 +++++ src/icons/src/run.ts | 21 ++ src/icons/tsconfig.json | 5 + src/icons/vite.config.js | 54 ++++ src/pnpm-lock.yaml | 406 +++++++++++++++++++++++++ src/pnpm-workspace.yaml | 1 + 20 files changed, 719 insertions(+), 8 deletions(-) create mode 100644 nginx/conf/locations/icons.conf create mode 100644 src/auth/config/default.png create mode 100644 src/icons/.dockerignore create mode 100644 src/icons/package.json create mode 100644 src/icons/src/app.ts create mode 100644 src/icons/src/openapi.ts create mode 100644 src/icons/src/plugins/README.md create mode 100644 src/icons/src/routes/set.ts create mode 100644 src/icons/src/run.ts create mode 100644 src/icons/tsconfig.json create mode 100644 src/icons/vite.config.js diff --git a/.gitignore b/.gitignore index 1ec2f88..195310c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ pnpm-lock.yaml package-lock.json .idea .dev +openapi-template +nginx-dev +src/redocly.yaml +flake/ diff --git a/Docker.mk b/Docker.mk index 6e5bec6..3bc0a48 100644 --- a/Docker.mk +++ b/Docker.mk @@ -34,7 +34,8 @@ ifeq "$(REDUCED_SET)" "y" tic-tac-toe \ nginx \ user \ - pong + pong \ + icons endif all: build diff --git a/docker-compose.yml b/docker-compose.yml index 6f5362b..a1a7f26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: - '9090:8443' volumes: # if you need to share files with nginx, you do it here. - - static-volume:/volumes/static + - icons-volume:/volumes/icons env_file: .env logging: driver: gelf @@ -72,7 +72,7 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static + - icons-volume:/volumes/icons - ./src/auth/config:/config env_file: .env logging: @@ -98,7 +98,32 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static + env_file: .env + logging: + driver: gelf + options: + gelf-address: "udp://127.0.0.1:12201" + tag: "{{.Name}}" + + + ############### + # ICONS # + ############### + icons: + build: + context: ./src/ + args: + - SERVICE=icons + additional_contexts: + pnpm_base: "service:pnpm_base" + pnpm_deps: "service:pnpm_deps" + container_name: app-icons + restart: always + networks: + - app + volumes: + - sqlite-volume:/volumes/database + - icons-volume:/volumes/icons env_file: .env logging: driver: gelf @@ -124,7 +149,6 @@ services: env_file: .env volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static logging: driver: gelf options: @@ -148,7 +172,6 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static env_file: .env logging: driver: gelf @@ -173,7 +196,6 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static env_file: .env logging: driver: gelf @@ -358,6 +380,6 @@ services: volumes: sqlite-volume: - static-volume: + icons-volume: grafana-data: elastic-data: diff --git a/nginx/conf/locations/icons.conf b/nginx/conf/locations/icons.conf new file mode 100644 index 0000000..386664d --- /dev/null +++ b/nginx/conf/locations/icons.conf @@ -0,0 +1,9 @@ +#forward the post request to the microservice +location /api/icons/ { + proxy_pass http://app-icons; +} + +location /icons/ { + root /volumes/; + default_type image/png; +} diff --git a/src/@dockerfiles/deps.Dockerfile b/src/@dockerfiles/deps.Dockerfile index df81482..8409f9b 100644 --- a/src/@dockerfiles/deps.Dockerfile +++ b/src/@dockerfiles/deps.Dockerfile @@ -10,5 +10,6 @@ COPY auth/package.json /build/auth/package.json COPY chat/package.json /build/chat/package.json COPY tic-tac-toe/package.json /build/tic-tac-toe/package.json COPY user/package.json /build/user/package.json +COPY icons/package.json /build/icons/package.json RUN pnpm install -q --frozen-lockfile; diff --git a/src/auth/config/default.png b/src/auth/config/default.png new file mode 100644 index 0000000000000000000000000000000000000000..bb9af0faca17e6618c62696fab5742af96ffe68c GIT binary patch literal 22473 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 => { 'guestLogin.failed.generic.unknown', ); } + await fs.cp('/config/default.png', `/volumes/icons/${user.id}`); return res.makeResponse(200, 'success', 'guestLogin.success', { token: this.signJwt('auth', user.id.toString()), }); diff --git a/src/auth/src/routes/oauth2/callback.ts b/src/auth/src/routes/oauth2/callback.ts index 36ec4a3..1a3689a 100644 --- a/src/auth/src/routes/oauth2/callback.ts +++ b/src/auth/src/routes/oauth2/callback.ts @@ -3,6 +3,7 @@ import { FastifyPluginAsync } from 'fastify'; import { Static, Type } from 'typebox'; import { typeResponse, isNullish } from '@shared/utils'; import * as oauth2 from '../../oauth2'; +import * as fs from 'node:fs/promises'; export const WhoAmIRes = Type.Union([ @@ -47,6 +48,9 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { user_name = `${orig}${Date.now()}`; } u = await this.db.createOauth2User(user_name, provider.display_name, userinfo.unique_id); + if (u) { + await fs.cp('/config/default.png', `/volumes/icons/${u.id}`); + } } if (isNullish(u)) { return res.code(500).send('failed to fetch or create user...'); diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index fe09163..094c8f1 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -2,6 +2,7 @@ import { FastifyPluginAsync } from 'fastify'; import { Static, Type } from 'typebox'; import { typeResponse, isNullish, MakeStaticResponse } from '@shared/utils'; +import * as fs from 'node:fs/promises'; const USERNAME_CHECK: RegExp = /^[a-zA-Z_0-9]+$/; @@ -61,6 +62,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { } const u = await this.db.createUser(name, user_name, password); if (isNullish(u)) { return res.makeResponse(500, 'failed', 'signin.failed.generic'); } + await fs.cp('/config/default.png', `/volumes/icons/${u.id}`); // every check has been passed, they are now logged in, using this token to say who they are... const userToken = this.signJwt('auth', u.id); diff --git a/src/icons/.dockerignore b/src/icons/.dockerignore new file mode 100644 index 0000000..c925c21 --- /dev/null +++ b/src/icons/.dockerignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/src/icons/package.json b/src/icons/package.json new file mode 100644 index 0000000..09922ab --- /dev/null +++ b/src/icons/package.json @@ -0,0 +1,35 @@ +{ + "type": "module", + "private": false, + "name": "icons", + "version": "1.0.0", + "description": "This project was bootstrapped with Fastify-CLI.", + "main": "app.ts", + "directories": { + "test": "test" + }, + "scripts": { + "start": "npm run build && node dist/run.js", + "build": "vite build", + "build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false", + "build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && node dist/openapi.cjs >openapi.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@fastify/formbody": "^8.0.2", + "@fastify/multipart": "^9.3.0", + "fastify": "^5.6.2", + "fastify-plugin": "^5.1.0", + "file-type": "^21.3.0", + "sharp": "^0.34.5", + "typebox": "^1.0.69" + }, + "devDependencies": { + "@types/node": "^22.19.3", + "rollup-plugin-node-externals": "^8.1.2", + "vite": "^7.3.0", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts new file mode 100644 index 0000000..07d3939 --- /dev/null +++ b/src/icons/src/app.ts @@ -0,0 +1,33 @@ +import { FastifyPluginAsync } from 'fastify'; +import * as db from '@shared/database'; +import * as auth from '@shared/auth'; +import * as swagger from '@shared/swagger'; +import * as utils from '@shared/utils'; + +declare const __SERVICE_NAME: string; + +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); + +const app: FastifyPluginAsync = async (fastify, opts): Promise => { + void opts; + await fastify.register(utils.useMakeResponse); + await fastify.register(utils.useMonitoring); + await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME }); + 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 FastifyPluginAsync, {}); + } + for (const route of Object.values(routes)) { + void fastify.register(route as FastifyPluginAsync, {}); + } +}; + +export default app; +export { app }; diff --git a/src/icons/src/openapi.ts b/src/icons/src/openapi.ts new file mode 100644 index 0000000..d66d7a7 --- /dev/null +++ b/src/icons/src/openapi.ts @@ -0,0 +1,21 @@ +import f, { FastifyPluginAsync } from 'fastify'; +import * as swagger from '@shared/swagger'; +import * as auth from '@shared/auth'; + +declare const __SERVICE_NAME: string; + +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); + +async function start() { + const fastify = f({ logger: false }); + await fastify.register(auth.authPlugin, { onlySchema: true }); + await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME }); + + for (const route of Object.values(routes)) { + await fastify.register(route as FastifyPluginAsync, {}); + } + await fastify.ready(); + console.log(JSON.stringify(fastify.swagger(), undefined, 4)); +} +start(); diff --git a/src/icons/src/plugins/README.md b/src/icons/src/plugins/README.md new file mode 100644 index 0000000..1e61ee5 --- /dev/null +++ b/src/icons/src/plugins/README.md @@ -0,0 +1,16 @@ +# Plugins Folder + +Plugins define behavior that is common to all the routes in your +application. Authentication, caching, templates, and all the other cross +cutting concerns should be handled by plugins placed in this folder. + +Files in this folder are typically defined through the +[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, +making them non-encapsulated. They can define decorators and set hooks +that will then be used in the rest of your application. + +Check out: + +* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/) +* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/). +* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/). diff --git a/src/icons/src/routes/set.ts b/src/icons/src/routes/set.ts new file mode 100644 index 0000000..5837a6c --- /dev/null +++ b/src/icons/src/routes/set.ts @@ -0,0 +1,72 @@ +import { FastifyPluginAsync } from 'fastify'; +import multipart from '@fastify/multipart'; +import { MakeStaticResponse, typeResponse } from '@shared/utils'; +import { fileTypeFromBuffer } from 'file-type'; +import sharp from 'sharp'; +import path from 'path'; +import fs from 'node:fs/promises'; + + +export const IconSetRes = { + '200': typeResponse('success', 'iconset.success'), + '400': typeResponse('success', ['iconset.failure.invalidFile', 'iconset.failure.noFile']), +}; + +export type IconSetRes = MakeStaticResponse; + +const validMimeTypes = new Set([ + 'image/jpeg', + 'image/png', +]); + +async function resizeAndSaveImage( + imageBuffer: Buffer, + filename: string, +): Promise { + const outputDir = '/volumes/icons/'; + const outputPath = path.join(outputDir, filename); + + // Ensure the directory exists + await fs.mkdir(outputDir, { recursive: true }); + + await sharp(imageBuffer) + .resize(512, 512, { + fit: 'cover', + }) + .png() + .toFile(outputPath); +} + +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; + await fastify.register(multipart); + fastify.post( + '/api/icons/set', + { schema: { response: IconSetRes, operationId: 'setIcons' }, config: { requireAuth: true } }, + async function(req, res) { + // req.authUser is always set, since this is gated + const userid = req.authUser!.id; + const file = await req.file(); + if (!file) { + return res.makeResponse(400, 'failure', 'iconset.failure.noFile'); + } + if (!validMimeTypes.has(file.mimetype)) { + return res.makeResponse(400, 'failure', 'iconset.failure.invalidFile'); + } + const buf = await file.toBuffer(); + if (!validMimeTypes.has((await fileTypeFromBuffer(buf))?.mime ?? 'unknown')) { + return res.makeResponse(400, 'failure', 'iconset.failure.invalidFile'); + } + try { + resizeAndSaveImage(buf, userid); + return res.makeResponse(200, 'success', 'iconset.success'); + } + catch (e: unknown) { + this.log.warn(e); + return res.makeResponse(400, 'failure', 'iconset.failure.invalidFile'); + } + }, + ); +}; + +export default route; diff --git a/src/icons/src/run.ts b/src/icons/src/run.ts new file mode 100644 index 0000000..3c59d5d --- /dev/null +++ b/src/icons/src/run.ts @@ -0,0 +1,21 @@ +// this sould only be used by the docker file ! + +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; + +const start = async () => { + const f: FastifyInstance = fastify({ logger: { level: 'info' } }); + process.on('SIGTERM', () => { + f.log.warn('Requested to shutdown'); + process.exit(134); + }); + try { + await f.register(app, {}); + await f.listen({ port: 80, host: '0.0.0.0' }); + } + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/icons/tsconfig.json b/src/icons/tsconfig.json new file mode 100644 index 0000000..e6d24e2 --- /dev/null +++ b/src/icons/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": {}, + "include": ["src/**/*.ts"] +} diff --git a/src/icons/vite.config.js b/src/icons/vite.config.js new file mode 100644 index 0000000..aa3ef08 --- /dev/null +++ b/src/icons/vite.config.js @@ -0,0 +1,54 @@ +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import nodeExternals from 'rollup-plugin-node-externals'; +import path from 'node:path'; +import fs from 'node:fs'; + +function collectDeps(...pkgJsonPaths) { + const allDeps = new Set(); + for (const pkgPath of pkgJsonPaths) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + for (const dep of Object.keys(pkg.dependencies || {})) { + allDeps.add(dep); + } + for (const peer of Object.keys(pkg.peerDependencies || {})) { + allDeps.add(peer); + } + } + return Array.from(allDeps); +} + +const externals = collectDeps( + './package.json', + '../@shared/package.json', +); + + +export default defineConfig({ + root: __dirname, + define: { + __SERVICE_NAME: '"icons"', + }, + // service root + plugins: [tsconfigPaths(), nodeExternals()], + build: { + ssr: true, + outDir: 'dist', + emptyOutDir: true, + lib: { + entry: path.resolve(__dirname, process.env.VITE_ENTRYPOINT ?? 'src/run.ts'), + // adjust main entry + formats: ['cjs'], + // CommonJS for Node.js + fileName: () => 'index.js', + }, + rollupOptions: { + external: externals, + }, + target: 'node22', + // or whatever Node version you use + sourcemap: true, + minify: false, + // for easier debugging + }, +}); diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index 0268c48..9571c8c 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -141,6 +141,43 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.3)(vite@7.3.0(@types/node@22.19.3)(yaml@2.8.2)) + icons: + dependencies: + '@fastify/formbody': + specifier: ^8.0.2 + version: 8.0.2 + '@fastify/multipart': + specifier: ^9.3.0 + version: 9.3.0 + fastify: + specifier: ^5.6.2 + version: 5.6.2 + fastify-plugin: + specifier: ^5.1.0 + version: 5.1.0 + file-type: + specifier: ^21.3.0 + version: 21.3.0 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + typebox: + specifier: ^1.0.69 + version: 1.0.69 + devDependencies: + '@types/node': + specifier: ^22.19.3 + version: 22.19.3 + rollup-plugin-node-externals: + specifier: ^8.1.2 + version: 8.1.2(rollup@4.54.0) + vite: + specifier: ^7.3.0 + version: 7.3.0(@types/node@22.19.3)(yaml@2.8.2) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.3)(vite@7.3.0(@types/node@22.19.3)(yaml@2.8.2)) + pong: dependencies: fastify: @@ -224,6 +261,12 @@ importers: packages: + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -424,15 +467,24 @@ packages: '@fastify/ajv-compiler@4.0.5': resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + '@fastify/cookie@11.0.2': resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} + '@fastify/deepmerge@3.1.0': + resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==} + '@fastify/error@4.2.0': resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} '@fastify/fast-json-stringify-compiler@5.0.3': resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + '@fastify/formbody@8.0.2': + resolution: {integrity: sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==} + '@fastify/forwarded@3.0.1': resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} @@ -442,6 +494,9 @@ packages: '@fastify/merge-json-schemas@0.2.1': resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + '@fastify/multipart@9.3.0': + resolution: {integrity: sha512-NpeKipTOjjL1dA7SSlRMrOWWtrE8/0yKOmeudkdQoEaz4sVDJw5MVdZIahsWhvpc3YTN7f04f9ep/Y65RKoOWA==} + '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} @@ -473,6 +528,143 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -605,6 +797,13 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/bcrypt@6.0.0': resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==} @@ -1042,6 +1241,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-type@21.3.0: + resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} + engines: {node: '>=20'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1497,6 +1700,10 @@ packages: sha1@1.1.1: resolution: {integrity: sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1587,6 +1794,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1617,6 +1828,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + ts-api-utils@2.3.0: resolution: {integrity: sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==} engines: {node: '>=18.12'} @@ -1633,6 +1848,9 @@ packages: typescript: optional: true + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -1655,6 +1873,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -1774,6 +1996,13 @@ packages: snapshots: + '@borewit/text-codec@0.2.1': {} + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -1906,17 +2135,26 @@ snapshots: ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.1.0 + '@fastify/busboy@3.2.0': {} + '@fastify/cookie@11.0.2': dependencies: cookie: 1.1.1 fastify-plugin: 5.1.0 + '@fastify/deepmerge@3.1.0': {} + '@fastify/error@4.2.0': {} '@fastify/fast-json-stringify-compiler@5.0.3': dependencies: fast-json-stringify: 6.1.1 + '@fastify/formbody@8.0.2': + dependencies: + fast-querystring: 1.1.2 + fastify-plugin: 5.1.0 + '@fastify/forwarded@3.0.1': {} '@fastify/jwt@9.1.0': @@ -1931,6 +2169,14 @@ snapshots: dependencies: dequal: 2.0.3 + '@fastify/multipart@9.3.0': + dependencies: + '@fastify/busboy': 3.2.0 + '@fastify/deepmerge': 3.1.0 + '@fastify/error': 4.2.0 + fastify-plugin: 5.1.0 + secure-json-parse: 4.1.0 + '@fastify/proxy-addr@5.1.0': dependencies: '@fastify/forwarded': 3.0.1 @@ -1982,6 +2228,102 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -2069,6 +2411,15 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@types/bcrypt@6.0.0': dependencies: '@types/node': 22.19.3 @@ -2584,6 +2935,15 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-type@21.3.0: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.4 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -3026,6 +3386,37 @@ snapshots: charenc: 0.0.2 crypt: 0.0.2 + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3136,6 +3527,10 @@ snapshots: strip-json-comments@3.1.1: {} + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3172,6 +3567,12 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + ts-api-utils@2.3.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -3180,6 +3581,9 @@ snapshots: optionalDependencies: typescript: 5.9.3 + tslib@2.8.1: + optional: true + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -3203,6 +3607,8 @@ snapshots: typescript@5.9.3: {} + uint8array-extras@1.5.0: {} + undici-types@6.21.0: {} undici-types@7.16.0: diff --git a/src/pnpm-workspace.yaml b/src/pnpm-workspace.yaml index 311c660..2840480 100644 --- a/src/pnpm-workspace.yaml +++ b/src/pnpm-workspace.yaml @@ -9,3 +9,4 @@ onlyBuiltDependencies: - core-js - esbuild - protobufjs + - sharp