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 0000000..bb9af0f Binary files /dev/null and b/src/auth/config/default.png differ diff --git a/src/auth/src/routes/guestLogin.ts b/src/auth/src/routes/guestLogin.ts index ce2626a..8d13aeb 100644 --- a/src/auth/src/routes/guestLogin.ts +++ b/src/auth/src/routes/guestLogin.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'; export const GuestLoginRes = { '500': typeResponse('failed', [ @@ -91,6 +92,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { '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