From 8e57ccc7f0566900730015ccfd81186c5c6d28ac Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Sat, 16 May 2026 15:32:56 +0100 Subject: [PATCH] MAI CALL - step 3 --- .claude/settings.local.json | 5 +- .env.example | 11 + docker-compose.yml | 36 ++ package.json | 2 + packages/storage/package.json | 24 ++ packages/storage/src/index.ts | 21 + packages/storage/src/interface.ts | 5 + packages/storage/src/minio.ts | 60 +++ packages/storage/tsconfig.json | 6 + pnpm-lock.yaml | 677 ++++++++++++++++++++++++++++++ scripts/storage-smoke.ts | 57 +++ turbo.json | 8 +- 12 files changed, 910 insertions(+), 2 deletions(-) create mode 100644 packages/storage/package.json create mode 100644 packages/storage/src/index.ts create mode 100644 packages/storage/src/interface.ts create mode 100644 packages/storage/src/minio.ts create mode 100644 packages/storage/tsconfig.json create mode 100644 scripts/storage-smoke.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 05ca200..81b947d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -19,7 +19,10 @@ "PowerShell($env:npm_config_verify_deps_before_run = \"false\"; corepack pnpm exec prisma migrate status --schema packages/db/prisma/schema.prisma 2>&1 | Select-Object -Last 25)", "Bash(pnpm --filter @repo/db exec prisma migrate dev --name add_maintenance_request)", "Bash(pnpm --filter @repo/db exec prisma db execute --schema=prisma/schema.prisma --stdin)", - "Bash(docker exec *)" + "Bash(docker exec *)", + "Bash(pnpm install *)", + "Bash(docker compose *)", + "Bash(pnpm tsx *)" ] } } diff --git a/.env.example b/.env.example index d134944..7096f9b 100644 --- a/.env.example +++ b/.env.example @@ -31,3 +31,14 @@ LOG_LEVEL="info" # Node environment — Next.js sets this automatically; included here for # packages that need it at module load time. NODE_ENV="development" + +# MinIO / S3-compatible object storage. Matches docker-compose.yml defaults. +# S3_FORCE_PATH_STYLE=true is required for MinIO (and other self-hosted S3 impls). +S3_ENDPOINT="http://localhost:9000" +S3_REGION="us-east-1" +S3_BUCKET="fieldops" +S3_ACCESS_KEY="fieldops" +S3_SECRET_KEY="fieldops123" +S3_FORCE_PATH_STYLE="true" +MINIO_ROOT_USER="fieldops" +MINIO_ROOT_PASSWORD="fieldops123" diff --git a/docker-compose.yml b/docker-compose.yml index 2ff1c61..249c4c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,5 +19,41 @@ services: timeout: 5s retries: 10 + minio: + image: minio/minio:latest + container_name: fieldops-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-fieldops} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-fieldops123} + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio-data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 5s + timeout: 5s + retries: 10 + + minio-init: + image: minio/mc:latest + depends_on: + minio: + condition: service_started + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-fieldops} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-fieldops123} + entrypoint: > + /bin/sh -c " + sleep 3 && + mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD && + mc mb -p local/fieldops || true && + mc anonymous set none local/fieldops + " + volumes: postgres-data: + minio-data: diff --git a/package.json b/package.json index 0310e14..789ec4a 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ }, "devDependencies": { "@repo/config": "workspace:*", + "dotenv": "^16.4.7", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.9", "rimraf": "^6.0.1", + "tsx": "^4.19.2", "turbo": "^2.3.3", "typescript": "^5.7.2" } diff --git a/packages/storage/package.json b/packages/storage/package.json new file mode 100644 index 0000000..6afcfd3 --- /dev/null +++ b/packages/storage/package.json @@ -0,0 +1,24 @@ +{ + "name": "@repo/storage", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "clean": "rimraf .turbo node_modules" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.787.0", + "@aws-sdk/s3-request-presigner": "^3.787.0" + }, + "devDependencies": { + "@repo/config": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.7.2" + } +} diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts new file mode 100644 index 0000000..bf9c69c --- /dev/null +++ b/packages/storage/src/index.ts @@ -0,0 +1,21 @@ +export type { ObjectStorage } from './interface.js'; +export { MinioStorage } from './minio.js'; + +import { MinioStorage } from './minio.js'; + +export function makeStorage(): MinioStorage { + const endpoint = process.env['S3_ENDPOINT']; + const region = process.env['S3_REGION'] ?? 'us-east-1'; + const bucket = process.env['S3_BUCKET']; + const accessKey = process.env['S3_ACCESS_KEY']; + const secretKey = process.env['S3_SECRET_KEY']; + const forcePathStyle = process.env['S3_FORCE_PATH_STYLE'] === 'true'; + + if (!endpoint || !bucket || !accessKey || !secretKey) { + throw new Error( + 'Missing required env vars: S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY', + ); + } + + return new MinioStorage({ endpoint, region, bucket, accessKey, secretKey, forcePathStyle }); +} diff --git a/packages/storage/src/interface.ts b/packages/storage/src/interface.ts new file mode 100644 index 0000000..196a538 --- /dev/null +++ b/packages/storage/src/interface.ts @@ -0,0 +1,5 @@ +export interface ObjectStorage { + signPut(key: string, contentType: string, byteSize: number, ttlSec: number): Promise<{ url: string; expiresAt: Date }>; + signGet(key: string, ttlSec: number): Promise<{ url: string; expiresAt: Date }>; + delete(key: string): Promise; +} diff --git a/packages/storage/src/minio.ts b/packages/storage/src/minio.ts new file mode 100644 index 0000000..de93396 --- /dev/null +++ b/packages/storage/src/minio.ts @@ -0,0 +1,60 @@ +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + DeleteObjectCommand, +} from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import type { ObjectStorage } from './interface.js'; + +interface MinioStorageOptions { + endpoint: string; + region: string; + bucket: string; + accessKey: string; + secretKey: string; + forcePathStyle: boolean; +} + +export class MinioStorage implements ObjectStorage { + private readonly client: S3Client; + private readonly bucket: string; + + constructor(opts: MinioStorageOptions) { + this.bucket = opts.bucket; + this.client = new S3Client({ + endpoint: opts.endpoint, + region: opts.region, + credentials: { + accessKeyId: opts.accessKey, + secretAccessKey: opts.secretKey, + }, + forcePathStyle: opts.forcePathStyle, + }); + } + + async signPut( + key: string, + contentType: string, + _byteSize: number, + ttlSec: number, + ): Promise<{ url: string; expiresAt: Date }> { + const cmd = new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + ContentType: contentType, + }); + const url = await getSignedUrl(this.client, cmd, { expiresIn: ttlSec }); + return { url, expiresAt: new Date(Date.now() + ttlSec * 1000) }; + } + + async signGet(key: string, ttlSec: number): Promise<{ url: string; expiresAt: Date }> { + const cmd = new GetObjectCommand({ Bucket: this.bucket, Key: key }); + const url = await getSignedUrl(this.client, cmd, { expiresIn: ttlSec }); + return { url, expiresAt: new Date(Date.now() + ttlSec * 1000) }; + } + + async delete(key: string): Promise { + await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key })); + } +} diff --git a/packages/storage/tsconfig.json b/packages/storage/tsconfig.json new file mode 100644 index 0000000..87d9126 --- /dev/null +++ b/packages/storage/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@repo/config/tsconfig/library.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05719ea..0832b3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@repo/config': specifier: workspace:* version: link:packages/config + dotenv: + specifier: ^16.4.7 + version: 16.6.1 prettier: specifier: ^3.4.2 version: 3.8.3 @@ -20,6 +23,9 @@ importers: rimraf: specifier: ^6.0.1 version: 6.1.3 + tsx: + specifier: ^4.19.2 + version: 4.22.0 turbo: specifier: ^2.3.3 version: 2.9.14 @@ -259,6 +265,25 @@ importers: specifier: ^5.7.2 version: 5.9.3 + packages/storage: + dependencies: + '@aws-sdk/client-s3': + specifier: ^3.787.0 + version: 3.1047.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.787.0 + version: 3.1047.0 + devDependencies: + '@repo/config': + specifier: workspace:* + version: link:../config + rimraf: + specifier: ^6.0.1 + version: 6.1.3 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + packages/ui: dependencies: class-variance-authority: @@ -316,6 +341,165 @@ packages: nodemailer: optional: true + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.1047.0': + resolution: {integrity: sha512-gk8g31eqvgf7eLCpkVjWs9KL7gYgkomt3FT2o9tbIe6goYrBheN2lHxhCsTn1zFYbt7EwrZXTGkQPIQNIN0c5w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.10': + resolution: {integrity: sha512-ZGFFlYynBR78Y/F8b/7y4i4sgW/iGwJSjoM7AZo5Et6vyr4/L0bunN+uzKMsvecCZyqcPp4RRK7Rs17l0kMujg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.8': + resolution: {integrity: sha512-fVfUCL/Xh2zINYMPZvj+iBn6XWouQf0DAnjaWCI9MkmqXzL2Iy5FoQB8O7syFe6gN6AH1ecDDU58T51Ou0kFkA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.36': + resolution: {integrity: sha512-gE+CGuPZD1eqUWGSrM8CXDjlwuPujIuwI+IlorD1wE2RcANKKT4jscB9GY1nTJbjmXzD18sycsYbgCG5m3n4/g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.38': + resolution: {integrity: sha512-cHZo3bV6zN9joDQ2AYVctfzHTKStxWKwnGu0z7GwCUC+DAtB3qL/+26l+a63RbmFbVvb1JK+0vJKodN3hRMwyw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.40': + resolution: {integrity: sha512-0NFGS9I3PD2yMveQqqpwpRdyZVStzgk0Yr2rZHh80kV/QNqQCK5lSrksvU3nBcRNSUF5Uk8rL3Xk0EVR+UVAnA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.40': + resolution: {integrity: sha512-IEIl+UQnrEjZP53TSl91e8LBephi4i1Mt9WZrMgN8pOg6xPOLZdkN1GhsEzjkMD1TQy4Fp2dwWA/9ToTQFOlLA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.41': + resolution: {integrity: sha512-h6BlclpsPGkx7Pv7ukr8oKVqN3jvxnH5n9ZIUQa8focr1ZkKd2MYiPJ2Nv9GI97dohJVJBfZAsTp/qoZL5R1pw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.36': + resolution: {integrity: sha512-eDQ6X7clTAOxXegOx4rGT1hyfusGEYdJGCGo0Ym2+CKeMQBjk+SJSxSVev11NJew5xJHJ/c3hryl2awKaxuSEA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.40': + resolution: {integrity: sha512-jaABbsoOkGlKg5kaHetYmUV6mWM57H89ia0Yksom1XxC847mfjmEVb4p7VijS1sjPbXjUii4cftJuwsl4MXkRg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.40': + resolution: {integrity: sha512-bfIrM8IIzbRtXRQWx/vNEUBLTImLZyX5uKk8uSdeSAZ4Mj3Yi4UnRJLK4FkQLWErbM3McpVLQ1DaM6XO66Ed5g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.12': + resolution: {integrity: sha512-MAG0Adg7FFEwuoeLbb5SBnXDW7S2EpNTwHnQ4h3pJqSKVQOhOmugyA1MfMh6AD4SAfx0lko4htZdwkNoLqFj5A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.11': + resolution: {integrity: sha512-xpobcctR1AHSrvkiArgTyLffn78Lt9unPMpa/yic9RKn+bOf/5M55UIM6RaPL5xKzI06/GSsTDywTWvzEAbyyw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.18': + resolution: {integrity: sha512-2noO+4ARfC+8vOIyvJvQE6bioVaTRkUcPvUoM/jgwXcweZnZovSZ6OCs/cs+NU2p7yvuwuJT/7LkTzBSj5pU4A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.11': + resolution: {integrity: sha512-CBC6+tVYaOJo7QXgN1zJ4Ba2f3/Cpy4eRViYFimXW/O5Mn8hBmgXXzHu4vy4ubT80YWnp8aCFygr7dTOa14yQg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.10': + resolution: {integrity: sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.10': + resolution: {integrity: sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.12': + resolution: {integrity: sha512-5eltYxKB4MfdQv7/VhWxRbAVQKow5dz9votRFigTYrWJHMQXwLMltlbk7KFWSZh5NDBySfmjT7Jv/DWfYCmDng==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.39': + resolution: {integrity: sha512-cimoQxecHHNad+lv2g7QJ24Cxqh1P0EULJSxyX4YD95BUIGeGRPumbdEXpHPxNkJRU99DVmh7u16Y+uhFu31Yw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.10': + resolution: {integrity: sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.40': + resolution: {integrity: sha512-QLpD+HNQtL1Mc49/GRa6RmZvi/TEYBWPevC9F3L+j96IoG3xOSRctdQfbkX0lETb3TX9QQXU1oGYDmAB+YJprA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.8': + resolution: {integrity: sha512-/Vw2M27w+0APfMDzDpvv8auA4WiJ4D22+lC61pMS2M8Wk+4IydeRqh5utbrh+A5gQRxgUYd/xz3tdv8nQlmiHg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.14': + resolution: {integrity: sha512-VuLXVmm7+lKVxqFcOItPkXhjbJ02iUfxkxheRu41SfWf6/xrZup2A2SwHZos/LeQGu3SBHeqTQht80Uo3ienPA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1047.0': + resolution: {integrity: sha512-taPZDq1Xh/o59KELbxalBQHuG4ct518d71kNDfw1SKpM+dGqc3tMUhsE7ma9+wPr8TdGspatP+wAP1A/uI42sA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.26': + resolution: {integrity: sha512-2N62veqdMZBCwQUHsbhtnaovOFjOa5Dn3dAD1nRqFTUXR4QmirT3HZnfus/L1DS08Vm5CkoKmL0iMVt6YbqEag==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1047.0': + resolution: {integrity: sha512-GwJUeMijpeO2SOGGLRg4q2Nj9foBUBd7hTALYVId+m8fQmA4P2hITp5dmrZFd4AjEkSVmt2eFqmk3TttF7HZeQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.8': + resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.9': + resolution: {integrity: sha512-ibx8Vd73rCTHekNGeXX8cpGWoBKbNAlwKHL3yjSxxttu5QnNDaSAM7/0MFYDjU31/F4lyrPoQcGirT0ew61xcg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.11': + resolution: {integrity: sha512-kq3RS6XQtHMrLFShbkem6h+8fxazB3jEIsbMC6aaSInOciRGE+eGAqTgJ+obO7Euo/pjM8thVqLiLISEH9X9DA==} + + '@aws-sdk/util-user-agent-node@3.973.26': + resolution: {integrity: sha512-9bHR/EERjhrUGyo1qW620ogbGBtCglYB/pEtcm85sVd4/Ah+bwdLI3g1aJf75oNwNwh7+fw+8wOk/OCWHjzVmA==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.24': + resolution: {integrity: sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} @@ -757,6 +941,9 @@ packages: cpu: [x64] os: [win32] + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -810,6 +997,42 @@ packages: '@prisma/get-platform@6.19.3': resolution: {integrity: sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==} + '@smithy/core@3.24.3': + resolution: {integrity: sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.3': + resolution: {integrity: sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.3': + resolution: {integrity: sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.3': + resolution: {integrity: sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.2': + resolution: {integrity: sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1037,6 +1260,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -1314,6 +1540,13 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.7.3: + resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1632,6 +1865,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1960,6 +2197,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -2089,6 +2329,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2110,6 +2354,369 @@ snapshots: preact: 10.11.3 preact-render-to-string: 5.2.3(preact@10.11.3) + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1047.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.10 + '@aws-sdk/credential-provider-node': 3.972.41 + '@aws-sdk/middleware-bucket-endpoint': 3.972.12 + '@aws-sdk/middleware-expect-continue': 3.972.11 + '@aws-sdk/middleware-flexible-checksums': 3.974.18 + '@aws-sdk/middleware-host-header': 3.972.11 + '@aws-sdk/middleware-location-constraint': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.12 + '@aws-sdk/middleware-sdk-s3': 3.972.39 + '@aws-sdk/middleware-ssec': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.40 + '@aws-sdk/region-config-resolver': 3.972.14 + '@aws-sdk/signature-v4-multi-region': 3.996.26 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.9 + '@aws-sdk/util-user-agent-browser': 3.972.11 + '@aws-sdk/util-user-agent-node': 3.973.26 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.974.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/xml-builder': 3.972.24 + '@smithy/core': 3.24.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.8': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.40': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/credential-provider-env': 3.972.36 + '@aws-sdk/credential-provider-http': 3.972.38 + '@aws-sdk/credential-provider-login': 3.972.40 + '@aws-sdk/credential-provider-process': 3.972.36 + '@aws-sdk/credential-provider-sso': 3.972.40 + '@aws-sdk/credential-provider-web-identity': 3.972.40 + '@aws-sdk/nested-clients': 3.997.8 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/credential-provider-imds': 4.3.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.40': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/nested-clients': 3.997.8 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.41': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.36 + '@aws-sdk/credential-provider-http': 3.972.38 + '@aws-sdk/credential-provider-ini': 3.972.40 + '@aws-sdk/credential-provider-process': 3.972.36 + '@aws-sdk/credential-provider-sso': 3.972.40 + '@aws-sdk/credential-provider-web-identity': 3.972.40 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/credential-provider-imds': 4.3.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.40': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/nested-clients': 3.997.8 + '@aws-sdk/token-providers': 3.1047.0 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.40': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/nested-clients': 3.997.8 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.12': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.18': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.974.10 + '@aws-sdk/crc64-nvme': 3.972.8 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.12': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.39': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/signature-v4-multi-region': 3.996.26 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.40': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.9 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.8': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.10 + '@aws-sdk/middleware-host-header': 3.972.11 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.12 + '@aws-sdk/middleware-user-agent': 3.972.40 + '@aws-sdk/region-config-resolver': 3.972.14 + '@aws-sdk/signature-v4-multi-region': 3.996.26 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.9 + '@aws-sdk/util-user-agent-browser': 3.972.11 + '@aws-sdk/util-user-agent-node': 3.973.26 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.14': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1047.0': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/signature-v4-multi-region': 3.996.26 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.26': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1047.0': + dependencies: + '@aws-sdk/core': 3.974.10 + '@aws-sdk/nested-clients': 3.997.8 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.8': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.9': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.2 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.26': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.40 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.24': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.2 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 @@ -2396,6 +3003,8 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.18': optional: true + '@nodable/entities@2.1.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2451,6 +3060,54 @@ snapshots: dependencies: '@prisma/debug': 6.19.3 + '@smithy/core@3.24.3': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/types@4.14.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.15': @@ -2674,6 +3331,8 @@ snapshots: binary-extensions@2.3.0: {} + bowser@2.14.1: {} + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -2978,6 +3637,18 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.7.3: + dependencies: + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3239,6 +3910,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -3514,6 +4187,8 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@2.3.0: {} + styled-jsx@5.1.6(react@19.2.6): dependencies: client-only: 0.0.1 @@ -3658,6 +4333,8 @@ snapshots: wrappy@1.0.2: {} + xml-naming@0.1.0: {} + yocto-queue@0.1.0: {} zod@3.25.76: {} diff --git a/scripts/storage-smoke.ts b/scripts/storage-smoke.ts new file mode 100644 index 0000000..1cecd95 --- /dev/null +++ b/scripts/storage-smoke.ts @@ -0,0 +1,57 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { config as loadEnv } from 'dotenv'; + +// Load .env from repo root before importing storage (env vars must be set first). +const here = path.dirname(fileURLToPath(import.meta.url)); +loadEnv({ path: path.resolve(here, '../.env') }); + +import { makeStorage } from '../packages/storage/src/index.js'; + +async function main() { + const storage = makeStorage(); + const testKey = `test/smoke-${Date.now()}.jpg`; + const content = 'fake-jpeg-content-for-smoke-test'; + + console.log('1. Generating presigned PUT URL...'); + const { url: putUrl, expiresAt: putExpiry } = await storage.signPut( + testKey, + 'image/jpeg', + content.length, + 300, + ); + console.log(` PUT URL (expires ${putExpiry.toISOString()}): ${putUrl.slice(0, 80)}...`); + + console.log('2. Uploading test content via presigned PUT...'); + const putRes = await fetch(putUrl, { + method: 'PUT', + body: content, + headers: { 'Content-Type': 'image/jpeg' }, + }); + if (!putRes.ok) { + throw new Error(`PUT failed: ${putRes.status} ${await putRes.text()}`); + } + console.log(' Upload OK'); + + console.log('3. Generating presigned GET URL...'); + const { url: getUrl, expiresAt: getExpiry } = await storage.signGet(testKey, 60); + console.log(` GET URL (expires ${getExpiry.toISOString()}): ${getUrl.slice(0, 80)}...`); + + console.log('4. Downloading and verifying content...'); + const getRes = await fetch(getUrl); + if (!getRes.ok) { + throw new Error(`GET failed: ${getRes.status} ${await getRes.text()}`); + } + const downloaded = await getRes.text(); + if (downloaded !== content) { + throw new Error(`Content mismatch!\n expected: "${content}"\n got: "${downloaded}"`); + } + console.log(' Download OK — content matches'); + + console.log('\nSmoke test PASSED'); +} + +main().catch((err) => { + console.error('Smoke test FAILED:', err); + process.exit(1); +}); diff --git a/turbo.json b/turbo.json index b512775..e911b7f 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,13 @@ "AUTH_SECRET", "AUTH_DEV_AUTOLOGIN", "NEXT_PUBLIC_APP_URL", - "LOG_LEVEL" + "LOG_LEVEL", + "S3_ENDPOINT", + "S3_REGION", + "S3_BUCKET", + "S3_ACCESS_KEY", + "S3_SECRET_KEY", + "S3_FORCE_PATH_STYLE" ], "tasks": { "build": {