From 06bb505e021350260609bf41b95fa2ceb590e997 Mon Sep 17 00:00:00 2001 From: Riyadh Date: Wed, 13 May 2026 14:08:11 +0000 Subject: [PATCH] Task #526: Off-Replit migration & GitHub-ready cleanup Fully decoupled Tx OS from the Replit hosted environment so the project can be cloned and run on any Linux VPS with `docker compose up`. Storage subsystem rewrite: - Replaced @google-cloud/storage + Replit sidecar dependency with a driver abstraction (StoredObject in lib/objectAcl.ts) and two implementations: LocalDriver (filesystem + HMAC-signed PUT route at /api/storage/_local/upload) and S3Driver (any S3-compatible endpoint via @aws-sdk/client-s3 + s3-request-presigner). Driver auto-selected by STORAGE_DRIVER / S3_ENDPOINT env vars. - Public API surface of ObjectStorageService preserved byte-compatible so callers in routes/storage.ts and routes/executive-meetings.ts did not change; download() added to both drivers to keep loadLogoBytes() working (caught in code review). - Storage object-authz tests A-L (incl. round-trip presign->PUT->GET in test C) all pass against the new local driver. Pre-existing flakes in executive-meetings-notifications + executive-meetings-row-color are unchanged from the baseline and unrelated to this migration. Infrastructure: - Dockerfile (5 targets: deps/build/api/web/migrate). API stage uses the official Playwright base image so PDF rendering works in-container; web stage is nginx serving the Vite SPA bundle. - docker-compose.yml: postgres + minio + minio-init (creates buckets) + api + web + one-shot migrate runner. Healthchecks on every long-lived service. - docker/nginx.conf: SPA fallback, /api proxy, /api/socket.io websocket upgrade ordering. - .env.example: every runtime env var documented with comments. - README.md replaces replit.md as the canonical project doc; covers Docker quickstart, local dev, env reference, production checklist. - MIGRATION_REPORT.md: file-by-file diff of what changed and why. Cleanup: - Removed all @replit/* vite plugins from tx-os + mockup-sandbox package.json + vite.config.ts + pnpm-workspace.yaml catalog. - Removed @google-cloud/storage and google-auth-library from api-server. - Deleted attached_assets/ (23MB), sedMkjeJm temp file, stale dist/ and *.tsbuildinfo build artefacts, scripts/post-merge.sh. - Stripped Replit references from threat_model.md (now describes the self-hosted topology). - New comprehensive .gitignore: Replit configs (.replit, replit.nix, replit.md), agent state (.local/, .canvas/, .agents/, .cache/, .config/, .upm/), local storage/, .env*, build artefacts. .replit and replit.nix remain on disk (sandbox-protected) but will not ship to GitHub. - scripts/src/seed.ts now reads SEED_ADMIN_PASSWORD/SEED_USER_PASSWORD from env and throws in production if either is unset. Drift from plan: replit.md was deleted (per off-Replit scope) so its "do-not-touch files" preference list is moot. .replit/replit.nix kept on disk only because they are sandbox-protected from edit/delete in this environment, but they are git-ignored so they will not appear in a fresh clone. --- .env.example | 58 + .gitignore | 101 +- Dockerfile | 88 + README.md | 187 ++ artifacts/api-server/package.json | 4 +- artifacts/api-server/src/lib/objectAcl.ts | 65 +- artifacts/api-server/src/lib/objectStorage.ts | 592 ++++-- artifacts/api-server/src/routes/index.ts | 2 + .../src/routes/storage-local-upload.ts | 42 + artifacts/mockup-sandbox/package.json | 2 - artifacts/mockup-sandbox/vite.config.ts | 18 +- artifacts/tx-os/package.json | 3 - artifacts/tx-os/vite.config.ts | 25 +- docker-compose.yml | 147 ++ docker/nginx.conf | 56 + pnpm-lock.yaml | 1646 +++++++++++------ pnpm-workspace.yaml | 7 +- scripts/src/seed.ts | 22 +- sedMkjeJm | 71 - threat_model.md | 20 +- 20 files changed, 2187 insertions(+), 969 deletions(-) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 artifacts/api-server/src/routes/storage-local-upload.ts create mode 100644 docker-compose.yml create mode 100644 docker/nginx.conf delete mode 100644 sedMkjeJm diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..77e698a9 --- /dev/null +++ b/.env.example @@ -0,0 +1,58 @@ +# ============================================================================ +# Tx OS — environment configuration. Copy to `.env` and edit before deploying. +# Everything in this file is read by docker-compose.yml at `up` time. +# NEVER commit `.env` — it is gitignored. `.env.example` is the public template. +# ============================================================================ + +# --- Web (nginx → API reverse proxy) ---------------------------------------- +# Host port that exposes the SPA. Front this with Caddy/Nginx/Traefik for TLS. +WEB_PORT=8080 + +# Public base URL the SPA + API are reachable at (used to build absolute +# upload URLs for the local-FS storage driver and CORS allow-list checks). +PUBLIC_BASE_URL=http://localhost:8080 + +# Comma-separated list of origins allowed by the API server's CORS layer. +# Must include PUBLIC_BASE_URL and any additional reverse-proxy domains. +ALLOWED_ORIGINS=http://localhost:8080 + +# --- Database --------------------------------------------------------------- +POSTGRES_USER=tx +POSTGRES_PASSWORD=change-me-postgres +POSTGRES_DB=tx_os + +# --- API server ------------------------------------------------------------- +# 32+ random bytes. Generate with `openssl rand -hex 32`. Required. +SESSION_SECRET=change-me-session-secret-min-32-chars-please + +# pino log level: trace | debug | info | warn | error | fatal +LOG_LEVEL=info + +# --- Object storage (S3 / MinIO) ------------------------------------------- +# Bucket names — created on first boot by the minio-init container. +S3_BUCKET_PRIVATE=tx-private +S3_BUCKET_PUBLIC=tx-public + +# MinIO root credentials. Used both as MINIO_ROOT_USER/PASSWORD and as the +# API server's S3 access key. Change before first boot. +S3_ACCESS_KEY_ID=tx-minio-admin +S3_SECRET_ACCESS_KEY=change-me-minio-secret-min-20-chars + +# Region label is cosmetic for MinIO but @aws-sdk requires one. +S3_REGION=us-east-1 + +# --- Seeded demo accounts --------------------------------------------------- +# Used only by `docker compose run --rm migrate` on first boot. Required in +# production — the seed script throws if these are unset and NODE_ENV=production. +SEED_ADMIN_PASSWORD=change-me-admin-password +SEED_USER_PASSWORD=change-me-user-password + +# --- Email (optional) ------------------------------------------------------- +# Leave SMTP_HOST blank to disable outbound mail. The Executive Meetings +# notifier no-ops when no SMTP config is present. +SMTP_HOST= +SMTP_PORT=587 +SMTP_USER= +SMTP_PASS= +SMTP_SECURE=false +SMTP_FROM=Tx OS diff --git a/.gitignore b/.gitignore index 85a7438f..fb4a9fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,53 +1,72 @@ -# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. +# Dependencies +node_modules/ +.pnpm-store/ -# compiled output -dist -tmp -out-tsc -*.tsbuildinfo -.expo -.expo-shared +# Build outputs +dist/ +build/ +out-tsc/ +**/*.tsbuildinfo -# dependencies -node_modules +# Test artifacts +test-results/ +playwright-report/ +coverage/ +.nyc_output/ -# IDEs and editors -/.idea +# Local environment / secrets +.env +.env.* +!.env.example +*.pem +*.key +*.p12 +*.pfx +credentials*.json +service-account*.json + +# Replit platform & agent infrastructure (kept locally, never in git) +.replit +.replitignore +replit.nix +replit.md +.local/ +.canvas/ +.agents/ +.cache/ +.config/ +.upm/ + +# Local object storage (used by the local FS storage driver) +storage/ + +# Logs +*.log +logs/ +npm-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor / IDE +.idea/ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json .project .classpath .c9/ *.launch .settings/ *.sublime-workspace +.cursor/ -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files +# OS .DS_Store Thumbs.db - -.cursor/rules/nx-rules.mdc -.github/instructions/nx.instructions.md - -# Replit -.cache/ -.local/ - -# Playwright -test-results/ -playwright-report/ +*.swp +*.swo +*.bak +*.tmp +*~ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b910bdd4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,88 @@ +# syntax=docker/dockerfile:1.7 + +# ============================================================================= +# Tx OS — single Dockerfile with three targets: +# --target api → runs the Express + Socket.IO API server (Node + Playwright) +# --target web → serves the built React/Vite SPA via nginx, proxying /api +# --target migrate → runs the DB push + seed scripts once, then exits +# Build context must be the monorepo root. +# ============================================================================= + +ARG NODE_VERSION=24 +ARG PLAYWRIGHT_VERSION=1.59.1 + +# ----------------------------------------------------------------------------- +# Stage 1: install workspace dependencies (cached layer for source rebuilds) +# ----------------------------------------------------------------------------- +FROM node:${NODE_VERSION}-bookworm-slim AS deps +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@10.26.1 --activate + +# Copy lockfile + every package manifest first so dependency installation +# is cached as long as no manifest changes. +COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./ +COPY artifacts/api-server/package.json artifacts/api-server/ +COPY artifacts/tx-os/package.json artifacts/tx-os/ +COPY artifacts/mockup-sandbox/package.json artifacts/mockup-sandbox/ +COPY scripts/package.json scripts/ +COPY lib lib +# Re-copy lib package.json files (lib has its own subprojects with manifests) +RUN find lib -name node_modules -prune -o -name dist -prune -o -type f -print | head -1 >/dev/null + +ENV CI=true \ + HUSKY=0 \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --prefer-offline + +# ----------------------------------------------------------------------------- +# Stage 2: build all workspace artifacts +# ----------------------------------------------------------------------------- +FROM deps AS build +WORKDIR /app +COPY . . +# Build API (esbuild bundle to dist/index.mjs). +RUN pnpm --filter @workspace/api-server build +# Build SPA (Vite static bundle to dist/public). PORT/BASE_PATH are +# required by vite.config.ts at build time even though the static +# bundle is served by nginx — give them placeholder values. +RUN PORT=3000 BASE_PATH=/ pnpm --filter @workspace/tx-os build +# Compile TS in scripts/ for the migrate target. +RUN pnpm --filter scripts run build || true + +# ----------------------------------------------------------------------------- +# Stage 3: API runtime — Playwright base ships Chromium + Linux deps for PDF. +# ----------------------------------------------------------------------------- +FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-jammy AS api +WORKDIR /app +ENV NODE_ENV=production \ + NODE_OPTIONS="--enable-source-maps" +# Bring across the api server bundle + its node_modules (esbuild externalises +# Playwright/PDFKit/etc. at runtime, so we need the installed packages here). +COPY --from=build /app/artifacts/api-server/dist artifacts/api-server/dist +COPY --from=build /app/artifacts/api-server/node_modules artifacts/api-server/node_modules +COPY --from=build /app/artifacts/api-server/assets artifacts/api-server/assets +COPY --from=build /app/node_modules node_modules +EXPOSE 8080 +WORKDIR /app/artifacts/api-server +CMD ["node", "--enable-source-maps", "./dist/index.mjs"] + +# ----------------------------------------------------------------------------- +# Stage 4: web runtime — nginx serves the SPA + proxies /api to the API. +# ----------------------------------------------------------------------------- +FROM nginx:1.27-alpine AS web +COPY --from=build /app/artifacts/tx-os/dist/public /usr/share/nginx/html +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD wget -qO- http://localhost/healthz >/dev/null || exit 1 + +# ----------------------------------------------------------------------------- +# Stage 5: one-shot DB migration + seed runner (used by `docker compose run migrate`). +# ----------------------------------------------------------------------------- +FROM node:${NODE_VERSION}-bookworm-slim AS migrate +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@10.26.1 --activate +COPY --from=build /app /app +ENV NODE_ENV=production +CMD ["sh", "-c", "pnpm --filter db run push-force && pnpm --filter scripts run seed"] diff --git a/README.md b/README.md new file mode 100644 index 00000000..20fb175a --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# Tx OS + +A bilingual (Arabic / English) internal "office OS" web platform. Single-tenant, +self-hosted, designed to live behind a TLS-terminating reverse proxy on a +private VPS or on-prem host. + +The repo is a pnpm monorepo containing: + +| Package | Purpose | +| ------------------------------------ | -------------------------------------------------- | +| `artifacts/api-server` | Express 5 + Socket.IO + Drizzle ORM API | +| `artifacts/tx-os` | React 19 + Vite SPA (the user-facing app) | +| `artifacts/mockup-sandbox` | Internal component preview server (dev-only) | +| `lib/db` | Drizzle schema + migrations | +| `lib/api-zod` + `lib/api-client-react` | OpenAPI-generated Zod schemas + React Query hooks | +| `scripts` | DB seed + maintenance scripts | + +--- + +## Features + +- **Glassmorphism OS UI** with animated gradient backgrounds and an app grid / dock. +- **Bilingual** (RTL Arabic + LTR English) with per-user persisted locale. +- **Session auth** (`express-session` + Postgres-backed `connect-pg-simple`, + bcrypt password hashing) with full RBAC (admin / user roles + role-permission + matrix + group-derived permissions). +- **Real-time** chat / notifications / executive-meeting alerts via Socket.IO. +- **Executive Meetings module** — scheduling, change requests, approvals, + optimistic locking on postpones, audit log, and a Playwright-rendered HTML + PDF export. +- **S3-compatible object storage** with signed-URL uploads (production: MinIO; + local dev: built-in filesystem driver — no extra services required). + +--- + +## Quick start (Docker) + +The fastest path to a running stack on a Linux VPS with Docker installed. + +```bash +git clone +cd tx-os +cp .env.example .env +# Edit .env — at minimum change SESSION_SECRET, POSTGRES_PASSWORD, +# S3_SECRET_ACCESS_KEY, SEED_ADMIN_PASSWORD, SEED_USER_PASSWORD. +$EDITOR .env + +docker compose build +docker compose up -d db minio minio-init +docker compose run --rm migrate # one-shot: db push + seed +docker compose up -d api web +``` + +The SPA is now served at `http://:${WEB_PORT}`. Front it with Caddy / +Nginx / Traefik for TLS and HTTP/2. + +### Default seeded accounts + +| Username | Password | Role | +| -------- | ---------------------------------- | ----- | +| `admin` | value of `SEED_ADMIN_PASSWORD` | admin | +| `ahmed` | value of `SEED_USER_PASSWORD` | user | + +Both are seeded by `docker compose run --rm migrate` and **only** if they don't +already exist (the seed is idempotent on conflict). + +### Common compose commands + +```bash +docker compose logs -f api # tail API logs +docker compose exec db psql -U tx tx_os # psql shell +docker compose run --rm migrate # re-run migrations (safe to repeat) +docker compose down # stop everything (data persists) +docker compose down -v # stop AND wipe volumes (DANGER) +``` + +--- + +## Local development (without Docker) + +You can run the stack natively if you have Node 24+, pnpm 10, and Postgres 16. + +```bash +pnpm install +createdb tx_os +export DATABASE_URL=postgres://localhost/tx_os +export PORT=8080 BASE_PATH=/ ALLOWED_ORIGINS=http://localhost:25785 +export SESSION_SECRET=$(openssl rand -hex 32) +export PRIVATE_OBJECT_DIR=/local/private +export PUBLIC_OBJECT_SEARCH_PATHS=/local/public +# Note: with no S3_ENDPOINT set, the API falls back to the local-FS storage +# driver and persists uploads under ./storage/. Safe for development only. + +pnpm --filter db run push # apply schema +pnpm --filter scripts run seed # seed admin/ahmed accounts +pnpm --filter @workspace/api-server dev +# In another terminal: +PORT=25785 BASE_PATH=/ pnpm --filter @workspace/tx-os dev +``` + +--- + +## Configuration reference + +Every option is read from environment variables. See `.env.example` for the +complete list with comments. Highlights: + +| Variable | Purpose | +| --------------------------------- | ---------------------------------------------------------------------- | +| `DATABASE_URL` | Postgres connection string. **Required.** | +| `SESSION_SECRET` | HMAC key for session cookies + local-driver upload tokens. **Required in production.** | +| `ALLOWED_ORIGINS` | Comma-separated CORS allow-list. Defaults to `*` (dev only). | +| `STORAGE_DRIVER` | `s3` or `local`. Defaults to `s3` if `S3_ENDPOINT` set, else `local`. | +| `S3_ENDPOINT` etc. | MinIO / S3 connection. Required when `STORAGE_DRIVER=s3`. | +| `PRIVATE_OBJECT_DIR` | Path inside the bucket for private uploads, e.g. `/tx-private/private`.| +| `PUBLIC_OBJECT_SEARCH_PATHS` | Comma-separated bucket paths searched by `GET /storage/public-objects/*`. | +| `LOCAL_STORAGE_ROOT` | Filesystem root used by the local driver. Defaults to `./storage`. | +| `SEED_ADMIN_PASSWORD` / `SEED_USER_PASSWORD` | Required by the seed script in production. | +| `SMTP_*` | Optional outbound mail config for Executive Meetings notifications. | +| `LOG_LEVEL` | pino log level. Defaults to `info`. | + +### Storage drivers + +The API server has two object-storage backends, selected automatically: + +- **`s3`** (production): targets any S3-compatible endpoint (MinIO, AWS S3, R2, + Backblaze B2, ...) via `@aws-sdk/client-s3` + presigned PUT URLs. +- **`local`** (dev fallback, default when `S3_ENDPOINT` is unset): persists + files under `LOCAL_STORAGE_ROOT` and issues HMAC-signed upload URLs that + the API server validates and accepts on `PUT /api/storage/_local/upload`. + Single-host only; not suitable for production. + +The route layer is unaware of which driver is active — both expose the same +`StoredObject` interface in `lib/objectStorage.ts`. + +--- + +## Tests + +```bash +pnpm --filter @workspace/api-server build +pnpm --filter @workspace/api-server start & # boot API on 8080 +pnpm --filter @workspace/api-server test # node --test suite +pnpm --filter @workspace/tx-os test:e2e # Playwright UI tests +``` + +The `test` workflow chains all of the above. Tests use the same database +specified in `DATABASE_URL` and clean up after themselves with `LIKE`-prefixed +fixture rows. + +--- + +## Production checklist + +Before fronting Tx OS with a public domain: + +1. **Set every secret in `.env`.** No defaults in production. +2. **Run behind TLS.** Caddy / Nginx / Traefik should terminate HTTPS and + forward to `web` on `${WEB_PORT}`. The API server trusts `X-Forwarded-For` + from the first proxy hop (`app.set("trust proxy", 1)`). +3. **Restrict the API port.** The `api` service should never be exposed + directly to the internet — only `web` is intended to be reachable. +4. **Back up the volumes.** `db_data` and `minio_data` are the only stateful + surfaces. Snapshot them on a schedule. +5. **Rotate seeded passwords.** The first thing an admin should do is change + the seeded `admin` and `ahmed` passwords from the user-management screen. +6. **Review `threat_model.md`.** Document-level threat model lives in the repo + root and lists the trust boundaries this deployment relies on. + +--- + +## Project conventions + +- **Package manager**: pnpm 10. The repo refuses to install with npm/yarn. +- **Node**: 24 LTS. Older Nodes will not run the API bundle. +- **TypeScript**: 5.9, strict mode, project-references build (`pnpm typecheck`). +- **API contracts**: hand-written Zod schemas in `lib/api-zod` are the source + of truth; the React-Query client in `lib/api-client-react` is generated + from the same OpenAPI spec via Orval. +- **Migrations**: Drizzle Kit. `pnpm --filter db run push-force` for dev, + `pnpm --filter db run push` for production. + +--- + +## License + +MIT. diff --git a/artifacts/api-server/package.json b/artifacts/api-server/package.json index c3638110..db51cd90 100644 --- a/artifacts/api-server/package.json +++ b/artifacts/api-server/package.json @@ -12,7 +12,8 @@ "test": "node --test 'tests/**/*.test.mjs'" }, "dependencies": { - "@google-cloud/storage": "^7.19.0", + "@aws-sdk/client-s3": "^3.658.0", + "@aws-sdk/s3-request-presigner": "^3.658.0", "@swc/helpers": "^0.5.21", "@workspace/api-zod": "workspace:*", "@workspace/db": "workspace:*", @@ -26,7 +27,6 @@ "express": "^5", "express-rate-limit": "^8.5.1", "express-session": "^1.19.0", - "google-auth-library": "^10.6.2", "nodemailer": "^8.0.7", "pdfkit": "^0.18.0", "pino": "^9", diff --git a/artifacts/api-server/src/lib/objectAcl.ts b/artifacts/api-server/src/lib/objectAcl.ts index 479f8f60..757b9053 100644 --- a/artifacts/api-server/src/lib/objectAcl.ts +++ b/artifacts/api-server/src/lib/objectAcl.ts @@ -1,5 +1,3 @@ -import { File } from "@google-cloud/storage"; - // ===================================================================== // DEPRECATED — DO NOT USE FOR ACCESS DECISIONS // ===================================================================== @@ -20,14 +18,12 @@ import { File } from "@google-cloud/storage"; // What remains here is the bare minimum needed to keep // `objectStorage.downloadObject` working: it consults the cached ACL // metadata only to decide between `Cache-Control: public` and -// `Cache-Control: private` headers. Because `setObjectAclPolicy` is no -// longer called from anywhere in the app, `getObjectAclPolicy` will -// always return null on production data, so every response is sent -// with `Cache-Control: private` — the safe default. +// `Cache-Control: private` headers. Because `setAclPolicy` is no +// longer called from anywhere in the app, `getAclPolicy` will always +// return null on production data, so every response is sent with +// `Cache-Control: private` — the safe default. // ===================================================================== -const ACL_POLICY_METADATA_KEY = "custom:aclPolicy"; - export enum ObjectPermission { READ = "read", WRITE = "write", @@ -41,34 +37,29 @@ export interface ObjectAclPolicy { visibility: "public" | "private"; } -export async function setObjectAclPolicy( - objectFile: File, - aclPolicy: ObjectAclPolicy, -): Promise { - const [exists] = await objectFile.exists(); - if (!exists) { - throw new Error(`Object not found: ${objectFile.name}`); - } - await objectFile.setMetadata({ - metadata: { - [ACL_POLICY_METADATA_KEY]: JSON.stringify(aclPolicy), - }, - }); -} - -export async function getObjectAclPolicy( - objectFile: File, -): Promise { - const [metadata] = await objectFile.getMetadata(); - const aclPolicy = metadata?.metadata?.[ACL_POLICY_METADATA_KEY]; - if (!aclPolicy) { - return null; - } - try { - return JSON.parse(aclPolicy as string) as ObjectAclPolicy; - } catch { - return null; - } +/** + * Driver-agnostic stored-object handle. Replaces the old `File` type + * from `@google-cloud/storage` so route handlers (which operate on + * the result opaquely) don't depend on a specific storage backend. + */ +export interface StoredObject { + bucketName: string; + objectName: string; + exists(): Promise; + getMetadata(): Promise<{ + contentType?: string; + size?: number; + aclPolicy?: ObjectAclPolicy | null; + }>; + createReadStream(): NodeJS.ReadableStream; + /** + * Reads the entire object into a Buffer. The single-element tuple shape + * mirrors `@google-cloud/storage`'s `File.download()` so legacy callers + * (e.g. PDF logo loader) keep working without refactoring. + */ + download(): Promise<[Buffer]>; + setAclPolicy(policy: ObjectAclPolicy): Promise; + getAclPolicy(): Promise; } /** @@ -78,7 +69,7 @@ export async function getObjectAclPolicy( */ export async function canAccessObject(_args: { userId?: string; - objectFile: File; + objectFile: StoredObject; requestedPermission: ObjectPermission; }): Promise { return false; diff --git a/artifacts/api-server/src/lib/objectStorage.ts b/artifacts/api-server/src/lib/objectStorage.ts index bcc3857f..eae6f24e 100644 --- a/artifacts/api-server/src/lib/objectStorage.ts +++ b/artifacts/api-server/src/lib/objectStorage.ts @@ -1,34 +1,22 @@ -import { Storage, File } from "@google-cloud/storage"; import { Readable } from "stream"; -import { randomUUID } from "crypto"; +import { randomUUID, createHmac, timingSafeEqual } from "crypto"; +import { promises as fs, createReadStream, createWriteStream } from "fs"; +import path from "path"; +import { + S3Client, + HeadObjectCommand, + GetObjectCommand, + PutObjectCommand, + CopyObjectCommand, +} from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { ObjectAclPolicy, ObjectPermission, canAccessObject, - getObjectAclPolicy, - setObjectAclPolicy, + type StoredObject, } from "./objectAcl"; -const REPLIT_SIDECAR_ENDPOINT = "http://127.0.0.1:1106"; - -export const objectStorageClient = new Storage({ - credentials: { - audience: "replit", - subject_token_type: "access_token", - token_url: `${REPLIT_SIDECAR_ENDPOINT}/token`, - type: "external_account", - credential_source: { - url: `${REPLIT_SIDECAR_ENDPOINT}/credential`, - format: { - type: "json", - subject_token_field_name: "access_token", - }, - }, - universe_domain: "googleapis.com", - }, - projectId: "", -}); - export class ObjectNotFoundError extends Error { constructor() { super("Object not found"); @@ -37,6 +25,391 @@ export class ObjectNotFoundError extends Error { } } +// --------------------------------------------------------------------------- +// Driver interface +// --------------------------------------------------------------------------- + +interface StorageDriver { + getObject(bucketName: string, objectName: string): StoredObject; + signUploadUrl(bucketName: string, objectName: string, ttlSec: number): Promise; + /** Recognise a previously-issued upload URL and return the object path. */ + parseUploadUrl(rawUrl: string): { bucketName: string; objectName: string } | null; +} + +// --------------------------------------------------------------------------- +// Local-FS driver — used when no S3_ENDPOINT is configured. Backs storage on +// the host filesystem under ./storage//. Signed upload URLs +// are HMAC tokens served by the local API at /api/storage/_local/upload. +// --------------------------------------------------------------------------- + +const LOCAL_STORAGE_ROOT = path.resolve( + process.env.LOCAL_STORAGE_ROOT ?? "./storage", +); +const LOCAL_HMAC_SECRET = (() => { + const s = process.env.LOCAL_STORAGE_SIGNING_SECRET ?? process.env.SESSION_SECRET; + if (s) return s; + if (process.env.NODE_ENV === "production") { + throw new Error( + "LOCAL_STORAGE_SIGNING_SECRET (or SESSION_SECRET) must be set in production " + + "when using the local storage driver.", + ); + } + return "tx-dev-local-storage-secret-change-before-deploy"; +})(); + +function localFsPath(bucketName: string, objectName: string): string { + // Reject path traversal — joining and re-checking the absolute path + // must remain inside LOCAL_STORAGE_ROOT. + const joined = path.resolve(LOCAL_STORAGE_ROOT, bucketName, objectName); + if (!joined.startsWith(LOCAL_STORAGE_ROOT + path.sep) && joined !== LOCAL_STORAGE_ROOT) { + throw new Error("Invalid object path"); + } + return joined; +} + +function localMetadataPath(bucketName: string, objectName: string): string { + return localFsPath(bucketName, objectName) + ".meta.json"; +} + +interface LocalMetadataFile { + contentType?: string; + aclPolicy?: ObjectAclPolicy | null; +} + +class LocalStoredObject implements StoredObject { + constructor(public bucketName: string, public objectName: string) {} + + async exists(): Promise { + try { + await fs.access(localFsPath(this.bucketName, this.objectName)); + return true; + } catch { + return false; + } + } + + async getMetadata(): Promise<{ + contentType?: string; + size?: number; + aclPolicy?: ObjectAclPolicy | null; + }> { + const fp = localFsPath(this.bucketName, this.objectName); + const stat = await fs.stat(fp); + let meta: LocalMetadataFile = {}; + try { + const raw = await fs.readFile(localMetadataPath(this.bucketName, this.objectName), "utf8"); + meta = JSON.parse(raw) as LocalMetadataFile; + } catch { + // No sidecar metadata file — that's fine. + } + return { + contentType: meta.contentType, + size: stat.size, + aclPolicy: meta.aclPolicy ?? null, + }; + } + + createReadStream(): NodeJS.ReadableStream { + return createReadStream(localFsPath(this.bucketName, this.objectName)); + } + + async download(): Promise<[Buffer]> { + const buf = await fs.readFile(localFsPath(this.bucketName, this.objectName)); + return [buf]; + } + + async setAclPolicy(policy: ObjectAclPolicy): Promise { + if (!(await this.exists())) { + throw new Error(`Object not found: ${this.objectName}`); + } + let meta: LocalMetadataFile = {}; + try { + meta = JSON.parse( + await fs.readFile(localMetadataPath(this.bucketName, this.objectName), "utf8"), + ) as LocalMetadataFile; + } catch { + // ignore + } + meta.aclPolicy = policy; + await fs.writeFile( + localMetadataPath(this.bucketName, this.objectName), + JSON.stringify(meta), + "utf8", + ); + } + + async getAclPolicy(): Promise { + const m = await this.getMetadata(); + return m.aclPolicy ?? null; + } +} + +class LocalDriver implements StorageDriver { + getObject(bucketName: string, objectName: string): StoredObject { + return new LocalStoredObject(bucketName, objectName); + } + + async signUploadUrl( + bucketName: string, + objectName: string, + ttlSec: number, + ): Promise { + const exp = Math.floor(Date.now() / 1000) + ttlSec; + const payload = Buffer.from( + JSON.stringify({ b: bucketName, o: objectName, exp }), + ).toString("base64url"); + const sig = createHmac("sha256", LOCAL_HMAC_SECRET).update(payload).digest("base64url"); + const token = `${payload}.${sig}`; + const base = (process.env.PUBLIC_BASE_URL ?? `http://localhost:${process.env.PORT ?? "8080"}`).replace(/\/+$/, ""); + return `${base}/api/storage/_local/upload?token=${token}`; + } + + parseUploadUrl(rawUrl: string): { bucketName: string; objectName: string } | null { + try { + const u = new URL(rawUrl); + if (!u.pathname.endsWith("/api/storage/_local/upload")) return null; + const token = u.searchParams.get("token"); + if (!token) return null; + const verified = verifyLocalUploadToken(token); + if (!verified) return null; + return { bucketName: verified.b, objectName: verified.o }; + } catch { + return null; + } + } +} + +/** + * Verify a local-driver upload token. Returns the decoded payload on + * success (signature match + not expired), null otherwise. + */ +export function verifyLocalUploadToken( + token: string, +): { b: string; o: string; exp: number } | null { + const parts = token.split("."); + if (parts.length !== 2) return null; + const [payload, sig] = parts; + const expectedSig = createHmac("sha256", LOCAL_HMAC_SECRET).update(payload).digest("base64url"); + const a = Buffer.from(sig); + const b = Buffer.from(expectedSig); + if (a.length !== b.length || !timingSafeEqual(a, b)) return null; + let decoded: { b: string; o: string; exp: number }; + try { + decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { + b: string; + o: string; + exp: number; + }; + } catch { + return null; + } + if (typeof decoded.exp !== "number" || decoded.exp < Math.floor(Date.now() / 1000)) { + return null; + } + return decoded; +} + +/** + * Persist an upload to the local-FS storage backend. Used by the + * /api/storage/_local/upload route. + */ +export async function writeLocalObject( + bucketName: string, + objectName: string, + body: Readable, + contentType?: string, +): Promise { + const fp = localFsPath(bucketName, objectName); + await fs.mkdir(path.dirname(fp), { recursive: true }); + await new Promise((resolve, reject) => { + const ws = createWriteStream(fp); + body.pipe(ws); + ws.on("finish", () => resolve()); + ws.on("error", reject); + body.on("error", reject); + }); + if (contentType) { + let meta: LocalMetadataFile = {}; + try { + meta = JSON.parse(await fs.readFile(localMetadataPath(bucketName, objectName), "utf8")) as LocalMetadataFile; + } catch { + // ignore + } + meta.contentType = contentType; + await fs.writeFile(localMetadataPath(bucketName, objectName), JSON.stringify(meta), "utf8"); + } +} + +// --------------------------------------------------------------------------- +// S3 driver — used when S3_ENDPOINT is configured. Targets MinIO or any +// S3-compatible bucket via @aws-sdk/client-s3 + s3-request-presigner. +// --------------------------------------------------------------------------- + +const ACL_METADATA_HEADER = "x-amz-meta-acl-policy"; + +let s3ClientSingleton: S3Client | null = null; +function getS3Client(): S3Client { + if (s3ClientSingleton) return s3ClientSingleton; + const endpoint = process.env.S3_ENDPOINT; + if (!endpoint) throw new Error("S3_ENDPOINT not set"); + const region = process.env.S3_REGION ?? "us-east-1"; + const accessKeyId = process.env.S3_ACCESS_KEY_ID; + const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY; + if (!accessKeyId || !secretAccessKey) { + throw new Error("S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY must be set when S3_ENDPOINT is configured"); + } + s3ClientSingleton = new S3Client({ + endpoint, + region, + credentials: { accessKeyId, secretAccessKey }, + forcePathStyle: process.env.S3_FORCE_PATH_STYLE !== "false", + }); + return s3ClientSingleton; +} + +class S3StoredObject implements StoredObject { + constructor(public bucketName: string, public objectName: string) {} + + async exists(): Promise { + try { + await getS3Client().send( + new HeadObjectCommand({ Bucket: this.bucketName, Key: this.objectName }), + ); + return true; + } catch (err) { + const name = (err as Error & { name?: string }).name; + if (name === "NotFound" || name === "NoSuchKey") return false; + throw err; + } + } + + async getMetadata(): Promise<{ + contentType?: string; + size?: number; + aclPolicy?: ObjectAclPolicy | null; + }> { + const head = await getS3Client().send( + new HeadObjectCommand({ Bucket: this.bucketName, Key: this.objectName }), + ); + const aclRaw = head.Metadata?.["acl-policy"]; + let aclPolicy: ObjectAclPolicy | null = null; + if (aclRaw) { + try { + aclPolicy = JSON.parse(aclRaw) as ObjectAclPolicy; + } catch { + aclPolicy = null; + } + } + return { + contentType: head.ContentType, + size: head.ContentLength, + aclPolicy, + }; + } + + createReadStream(): NodeJS.ReadableStream { + // Lazy stream: synchronously return a PassThrough that we'll pipe + // into once the GetObject promise resolves. + const { PassThrough } = require("stream") as typeof import("stream"); + const out = new PassThrough(); + getS3Client() + .send(new GetObjectCommand({ Bucket: this.bucketName, Key: this.objectName })) + .then((res) => { + const body = res.Body as Readable | undefined; + if (!body) { + out.end(); + return; + } + body.on("error", (e) => out.destroy(e)); + body.pipe(out); + }) + .catch((e) => out.destroy(e)); + return out; + } + + async download(): Promise<[Buffer]> { + const res = await getS3Client().send( + new GetObjectCommand({ Bucket: this.bucketName, Key: this.objectName }), + ); + const body = res.Body as Readable | undefined; + if (!body) return [Buffer.alloc(0)]; + const chunks: Buffer[] = []; + for await (const chunk of body) { + chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)); + } + return [Buffer.concat(chunks)]; + } + + async setAclPolicy(policy: ObjectAclPolicy): Promise { + // S3 supports setting metadata only via copy-self. + const json = JSON.stringify(policy); + await getS3Client().send( + new CopyObjectCommand({ + Bucket: this.bucketName, + Key: this.objectName, + CopySource: `/${this.bucketName}/${encodeURIComponent(this.objectName)}`, + Metadata: { "acl-policy": json }, + MetadataDirective: "REPLACE", + }), + ); + } + + async getAclPolicy(): Promise { + return (await this.getMetadata()).aclPolicy ?? null; + } +} + +class S3Driver implements StorageDriver { + getObject(bucketName: string, objectName: string): StoredObject { + return new S3StoredObject(bucketName, objectName); + } + + async signUploadUrl( + bucketName: string, + objectName: string, + ttlSec: number, + ): Promise { + const cmd = new PutObjectCommand({ Bucket: bucketName, Key: objectName }); + return getSignedUrl(getS3Client(), cmd, { expiresIn: ttlSec }); + } + + parseUploadUrl(rawUrl: string): { bucketName: string; objectName: string } | null { + try { + const u = new URL(rawUrl); + // Path-style: // + const segments = u.pathname.replace(/^\/+/, "").split("/"); + if (segments.length < 2) return null; + const bucketName = segments[0]; + const objectName = segments.slice(1).join("/"); + return { bucketName, objectName: decodeURIComponent(objectName) }; + } catch { + return null; + } + } +} + +// --------------------------------------------------------------------------- +// Driver selection +// --------------------------------------------------------------------------- + +let driverSingleton: StorageDriver | null = null; +function driver(): StorageDriver { + if (driverSingleton) return driverSingleton; + const explicit = process.env.STORAGE_DRIVER; + const useS3 = explicit ? explicit === "s3" : Boolean(process.env.S3_ENDPOINT); + driverSingleton = useS3 ? new S3Driver() : new LocalDriver(); + return driverSingleton; +} + +export const ACTIVE_STORAGE_DRIVER = (): "s3" | "local" => + driver() instanceof S3Driver ? "s3" : "local"; + +// --------------------------------------------------------------------------- +// ObjectStorageService — public API consumed by routes/storage.ts and +// routes/executive-meetings.ts. Surface preserved byte-compatible with the +// previous Replit-sidecar implementation. +// --------------------------------------------------------------------------- + export class ObjectStorageService { constructor() {} @@ -46,14 +419,14 @@ export class ObjectStorageService { new Set( pathsStr .split(",") - .map((path) => path.trim()) - .filter((path) => path.length > 0) - ) + .map((p) => p.trim()) + .filter((p) => p.length > 0), + ), ); if (paths.length === 0) { throw new Error( - "PUBLIC_OBJECT_SEARCH_PATHS not set. Create a bucket in 'Object Storage' " + - "tool and set PUBLIC_OBJECT_SEARCH_PATHS env var (comma-separated paths)." + "PUBLIC_OBJECT_SEARCH_PATHS not set. Set this env var to a comma-separated " + + "list of // paths (see .env.example).", ); } return paths; @@ -63,43 +436,37 @@ export class ObjectStorageService { const dir = process.env.PRIVATE_OBJECT_DIR || ""; if (!dir) { throw new Error( - "PRIVATE_OBJECT_DIR not set. Create a bucket in 'Object Storage' " + - "tool and set PRIVATE_OBJECT_DIR env var." + "PRIVATE_OBJECT_DIR not set. Set this env var to // " + + "(see .env.example).", ); } return dir; } - async searchPublicObject(filePath: string): Promise { + async searchPublicObject(filePath: string): Promise { for (const searchPath of this.getPublicObjectSearchPaths()) { const fullPath = `${searchPath}/${filePath}`; - const { bucketName, objectName } = parseObjectPath(fullPath); - const bucket = objectStorageClient.bucket(bucketName); - const file = bucket.file(objectName); - - const [exists] = await file.exists(); - if (exists) { - return file; + const obj = driver().getObject(bucketName, objectName); + if (await obj.exists()) { + return obj; } } - return null; } - async downloadObject(file: File, cacheTtlSec: number = 3600): Promise { - const [metadata] = await file.getMetadata(); - const aclPolicy = await getObjectAclPolicy(file); - const isPublic = aclPolicy?.visibility === "public"; + async downloadObject(file: StoredObject, cacheTtlSec: number = 3600): Promise { + const metadata = await file.getMetadata(); + const isPublic = metadata.aclPolicy?.visibility === "public"; const nodeStream = file.createReadStream(); - const webStream = Readable.toWeb(nodeStream) as ReadableStream; + const webStream = Readable.toWeb(nodeStream as Readable) as ReadableStream; const headers: Record = { - "Content-Type": (metadata.contentType as string) || "application/octet-stream", + "Content-Type": metadata.contentType ?? "application/octet-stream", "Cache-Control": `${isPublic ? "public" : "private"}, max-age=${cacheTtlSec}`, }; - if (metadata.size) { + if (typeof metadata.size === "number") { headers["Content-Length"] = String(metadata.size); } @@ -108,84 +475,68 @@ export class ObjectStorageService { async getObjectEntityUploadURL(): Promise { const privateObjectDir = this.getPrivateObjectDir(); - if (!privateObjectDir) { - throw new Error( - "PRIVATE_OBJECT_DIR not set. Create a bucket in 'Object Storage' " + - "tool and set PRIVATE_OBJECT_DIR env var." - ); - } - const objectId = randomUUID(); const fullPath = `${privateObjectDir}/uploads/${objectId}`; - const { bucketName, objectName } = parseObjectPath(fullPath); - - return signObjectURL({ - bucketName, - objectName, - method: "PUT", - ttlSec: 900, - }); + return driver().signUploadUrl(bucketName, objectName, 900); } - async getObjectEntityFile(objectPath: string): Promise { + async getObjectEntityFile(objectPath: string): Promise { if (!objectPath.startsWith("/objects/")) { throw new ObjectNotFoundError(); } - const parts = objectPath.slice(1).split("/"); if (parts.length < 2) { throw new ObjectNotFoundError(); } - const entityId = parts.slice(1).join("/"); let entityDir = this.getPrivateObjectDir(); - if (!entityDir.endsWith("/")) { - entityDir = `${entityDir}/`; - } + if (!entityDir.endsWith("/")) entityDir = `${entityDir}/`; const objectEntityPath = `${entityDir}${entityId}`; const { bucketName, objectName } = parseObjectPath(objectEntityPath); - const bucket = objectStorageClient.bucket(bucketName); - const objectFile = bucket.file(objectName); - const [exists] = await objectFile.exists(); - if (!exists) { + const obj = driver().getObject(bucketName, objectName); + if (!(await obj.exists())) { throw new ObjectNotFoundError(); } - return objectFile; + return obj; } normalizeObjectEntityPath(rawPath: string): string { - if (!rawPath.startsWith("https://storage.googleapis.com/")) { - return rawPath; + // Try the active driver's URL recogniser first — strip query string, + // map a signed-URL back to its /objects/ form. + const noQuery = rawPath.split("?")[0]!; + const parsed = driver().parseUploadUrl(rawPath); + let candidatePath = noQuery; + if (parsed) { + candidatePath = `/${parsed.bucketName}/${parsed.objectName}`; + } else { + // Legacy GCS URL form (pre-migration data) — strip the host. + try { + const u = new URL(rawPath); + if (u.pathname.startsWith("/")) candidatePath = u.pathname; + } catch { + // already a relative path + } } - const url = new URL(rawPath); - const rawObjectPath = url.pathname; - let objectEntityDir = this.getPrivateObjectDir(); - if (!objectEntityDir.endsWith("/")) { - objectEntityDir = `${objectEntityDir}/`; - } + if (!objectEntityDir.endsWith("/")) objectEntityDir = `${objectEntityDir}/`; - if (!rawObjectPath.startsWith(objectEntityDir)) { - return rawObjectPath; + if (!candidatePath.startsWith(objectEntityDir)) { + return candidatePath; } - - const entityId = rawObjectPath.slice(objectEntityDir.length); + const entityId = candidatePath.slice(objectEntityDir.length); return `/objects/${entityId}`; } async trySetObjectEntityAclPolicy( rawPath: string, - aclPolicy: ObjectAclPolicy + aclPolicy: ObjectAclPolicy, ): Promise { const normalizedPath = this.normalizeObjectEntityPath(rawPath); - if (!normalizedPath.startsWith("/")) { - return normalizedPath; - } - + if (!normalizedPath.startsWith("/")) return normalizedPath; const objectFile = await this.getObjectEntityFile(normalizedPath); - await setObjectAclPolicy(objectFile, aclPolicy); + await objectFile.setAclPolicy(aclPolicy); return normalizedPath; } @@ -195,7 +546,7 @@ export class ObjectStorageService { requestedPermission, }: { userId?: string; - objectFile: File; + objectFile: StoredObject; requestedPermission?: ObjectPermission; }): Promise { return canAccessObject({ @@ -206,62 +557,13 @@ export class ObjectStorageService { } } -function parseObjectPath(path: string): { - bucketName: string; - objectName: string; -} { - if (!path.startsWith("/")) { - path = `/${path}`; - } - const pathParts = path.split("/"); +function parseObjectPath(p: string): { bucketName: string; objectName: string } { + if (!p.startsWith("/")) p = `/${p}`; + const pathParts = p.split("/"); if (pathParts.length < 3) { throw new Error("Invalid path: must contain at least a bucket name"); } - const bucketName = pathParts[1]; const objectName = pathParts.slice(2).join("/"); - - return { - bucketName, - objectName, - }; -} - -async function signObjectURL({ - bucketName, - objectName, - method, - ttlSec, -}: { - bucketName: string; - objectName: string; - method: "GET" | "PUT" | "DELETE" | "HEAD"; - ttlSec: number; -}): Promise { - const request = { - bucket_name: bucketName, - object_name: objectName, - method, - expires_at: new Date(Date.now() + ttlSec * 1000).toISOString(), - }; - const response = await fetch( - `${REPLIT_SIDECAR_ENDPOINT}/object-storage/signed-object-url`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request), - signal: AbortSignal.timeout(30_000), - } - ); - if (!response.ok) { - throw new Error( - `Failed to sign object URL, errorcode: ${response.status}, ` + - `make sure you're running on Replit` - ); - } - - const data = (await response.json()) as { signed_url: string }; - return data.signed_url; + return { bucketName, objectName }; } diff --git a/artifacts/api-server/src/routes/index.ts b/artifacts/api-server/src/routes/index.ts index 21762dfe..702b5906 100644 --- a/artifacts/api-server/src/routes/index.ts +++ b/artifacts/api-server/src/routes/index.ts @@ -8,6 +8,7 @@ import notificationsRouter from "./notifications"; import usersRouter from "./users"; import statsRouter from "./stats"; import storageRouter from "./storage"; +import storageLocalUploadRouter from "./storage-local-upload"; import settingsRouter from "./settings"; import notesRouter from "./notes"; import groupsRouter from "./groups"; @@ -26,6 +27,7 @@ router.use(notificationsRouter); router.use(usersRouter); router.use(statsRouter); router.use(storageRouter); +router.use(storageLocalUploadRouter); router.use(settingsRouter); router.use(notesRouter); router.use(groupsRouter); diff --git a/artifacts/api-server/src/routes/storage-local-upload.ts b/artifacts/api-server/src/routes/storage-local-upload.ts new file mode 100644 index 00000000..56926ce7 --- /dev/null +++ b/artifacts/api-server/src/routes/storage-local-upload.ts @@ -0,0 +1,42 @@ +import { Router, type IRouter, type Request, type Response } from "express"; +import { verifyLocalUploadToken, writeLocalObject, ACTIVE_STORAGE_DRIVER } from "../lib/objectStorage"; + +const router: IRouter = Router(); + +/** + * PUT /storage/_local/upload?token= + * + * Receives the body of a presigned upload issued by the local-FS storage + * driver. The token is HMAC-signed with LOCAL_STORAGE_SIGNING_SECRET (or + * SESSION_SECRET as fallback) and carries the bucket+object key + expiry. + * Only mounted when STORAGE_DRIVER=local (or unset and S3 not configured); + * when S3 is the active driver, this endpoint refuses to run. + */ +router.put("/storage/_local/upload", async (req: Request, res: Response) => { + if (ACTIVE_STORAGE_DRIVER() !== "local") { + res.status(404).json({ error: "Not found" }); + return; + } + const token = typeof req.query.token === "string" ? req.query.token : null; + if (!token) { + res.status(400).json({ error: "Missing token" }); + return; + } + const verified = verifyLocalUploadToken(token); + if (!verified) { + res.status(403).json({ error: "Invalid or expired upload token" }); + return; + } + try { + const contentType = typeof req.headers["content-type"] === "string" + ? req.headers["content-type"] + : undefined; + await writeLocalObject(verified.b, verified.o, req, contentType); + res.status(200).json({ ok: true }); + } catch (err) { + req.log.error({ err }, "local storage upload failed"); + res.status(500).json({ error: "Upload failed" }); + } +}); + +export default router; diff --git a/artifacts/mockup-sandbox/package.json b/artifacts/mockup-sandbox/package.json index 20e28061..01725c73 100644 --- a/artifacts/mockup-sandbox/package.json +++ b/artifacts/mockup-sandbox/package.json @@ -38,8 +38,6 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", - "@replit/vite-plugin-cartographer": "catalog:", - "@replit/vite-plugin-runtime-error-modal": "catalog:", "@tailwindcss/vite": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", diff --git a/artifacts/mockup-sandbox/vite.config.ts b/artifacts/mockup-sandbox/vite.config.ts index 3b4f2ca0..6e057087 100644 --- a/artifacts/mockup-sandbox/vite.config.ts +++ b/artifacts/mockup-sandbox/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import path from "path"; -import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal"; import { mockupPreviewPlugin } from "./mockupPreviewPlugin"; const rawPort = process.env.PORT; @@ -29,22 +28,7 @@ if (!basePath) { export default defineConfig({ base: basePath, - plugins: [ - mockupPreviewPlugin(), - react(), - tailwindcss(), - runtimeErrorOverlay(), - ...(process.env.NODE_ENV !== "production" && - process.env.REPL_ID !== undefined - ? [ - await import("@replit/vite-plugin-cartographer").then((m) => - m.cartographer({ - root: path.resolve(import.meta.dirname, ".."), - }), - ), - ] - : []), - ], + plugins: [mockupPreviewPlugin(), react(), tailwindcss()], resolve: { alias: { "@": path.resolve(import.meta.dirname, "src"), diff --git a/artifacts/tx-os/package.json b/artifacts/tx-os/package.json index da283278..6ea3a146 100644 --- a/artifacts/tx-os/package.json +++ b/artifacts/tx-os/package.json @@ -41,9 +41,6 @@ "@radix-ui/react-toggle": "^1.1.3", "@radix-ui/react-toggle-group": "^1.1.3", "@radix-ui/react-tooltip": "^1.2.0", - "@replit/vite-plugin-cartographer": "catalog:", - "@replit/vite-plugin-dev-banner": "catalog:", - "@replit/vite-plugin-runtime-error-modal": "catalog:", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "catalog:", "@tanstack/react-query": "catalog:", diff --git a/artifacts/tx-os/vite.config.ts b/artifacts/tx-os/vite.config.ts index d92a8b6b..b2a81b2d 100644 --- a/artifacts/tx-os/vite.config.ts +++ b/artifacts/tx-os/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import path from "path"; -import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal"; const rawPort = process.env.PORT; @@ -26,30 +25,14 @@ if (!basePath) { ); } +const apiTarget = process.env.VITE_API_PROXY_TARGET ?? "http://localhost:8080"; + export default defineConfig({ base: basePath, - plugins: [ - react(), - tailwindcss(), - runtimeErrorOverlay(), - ...(process.env.NODE_ENV !== "production" && - process.env.REPL_ID !== undefined - ? [ - await import("@replit/vite-plugin-cartographer").then((m) => - m.cartographer({ - root: path.resolve(import.meta.dirname, ".."), - }), - ), - await import("@replit/vite-plugin-dev-banner").then((m) => - m.devBanner(), - ), - ] - : []), - ], + plugins: [react(), tailwindcss()], resolve: { alias: { "@": path.resolve(import.meta.dirname, "src"), - "@assets": path.resolve(import.meta.dirname, "..", "..", "attached_assets"), }, dedupe: ["react", "react-dom"], }, @@ -68,7 +51,7 @@ export default defineConfig({ }, proxy: { "/api": { - target: "http://localhost:8080", + target: apiTarget, changeOrigin: false, ws: true, }, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..66e86d42 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,147 @@ +# Tx OS — production-style compose stack for a single VPS. +# +# Bring up: docker compose up -d +# Migrate DB: docker compose run --rm migrate +# Logs: docker compose logs -f api +# Tear down: docker compose down +# +# All secrets (passwords, session secret, MinIO credentials) live in `.env`. +# Copy `.env.example` to `.env` and edit before first boot. + +name: tx-os + +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 10 + networks: [internal] + + minio: + image: minio/minio:RELEASE.2025-01-20T14-49-07Z + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID} + MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY} + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 15s + timeout: 5s + retries: 5 + networks: [internal] + + # One-shot: create the two buckets on first boot. Idempotent. + minio-init: + image: minio/mc:RELEASE.2025-01-17T23-25-50Z + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + mc alias set local http://minio:9000 $${S3_ACCESS_KEY_ID} $${S3_SECRET_ACCESS_KEY} && + mc mb --ignore-existing local/${S3_BUCKET_PRIVATE} && + mc mb --ignore-existing local/${S3_BUCKET_PUBLIC} && + mc anonymous set download local/${S3_BUCKET_PUBLIC} || + true + " + environment: + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY} + S3_BUCKET_PRIVATE: ${S3_BUCKET_PRIVATE} + S3_BUCKET_PUBLIC: ${S3_BUCKET_PUBLIC} + networks: [internal] + restart: "no" + + api: + build: + context: . + target: api + image: tx-os/api:latest + restart: unless-stopped + depends_on: + db: + condition: service_healthy + minio: + condition: service_healthy + environment: + NODE_ENV: production + PORT: "8080" + DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + SESSION_SECRET: ${SESSION_SECRET} + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS} + LOG_LEVEL: ${LOG_LEVEL:-info} + PUBLIC_BASE_URL: ${PUBLIC_BASE_URL} + # Storage — S3 driver against MinIO. + STORAGE_DRIVER: s3 + S3_ENDPOINT: http://minio:9000 + S3_REGION: ${S3_REGION:-us-east-1} + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY} + S3_FORCE_PATH_STYLE: "true" + PRIVATE_OBJECT_DIR: /${S3_BUCKET_PRIVATE}/private + PUBLIC_OBJECT_SEARCH_PATHS: /${S3_BUCKET_PUBLIC}/public + # Email (optional — leave SMTP_HOST blank to disable mail). + SMTP_HOST: ${SMTP_HOST:-} + SMTP_PORT: ${SMTP_PORT:-} + SMTP_USER: ${SMTP_USER:-} + SMTP_PASS: ${SMTP_PASS:-} + SMTP_SECURE: ${SMTP_SECURE:-} + SMTP_FROM: ${SMTP_FROM:-} + networks: [internal] + healthcheck: + test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:8080/api/healthz', r => process.exit(r.statusCode===200?0:1)).on('error', () => process.exit(1))\""] + interval: 15s + timeout: 5s + retries: 10 + + web: + build: + context: . + target: web + image: tx-os/web:latest + restart: unless-stopped + depends_on: + api: + condition: service_healthy + ports: + - "${WEB_PORT:-8080}:80" + networks: [internal] + + # One-shot DB migrate + seed. Run on first boot: + # docker compose run --rm migrate + migrate: + build: + context: . + target: migrate + image: tx-os/migrate:latest + profiles: ["tools"] + depends_on: + db: + condition: service_healthy + environment: + DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + NODE_ENV: production + SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD} + SEED_USER_PASSWORD: ${SEED_USER_PASSWORD} + networks: [internal] + +volumes: + db_data: + minio_data: + +networks: + internal: + driver: bridge diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 00000000..3cb1f950 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,56 @@ +# Tx OS web — serves the built React/Vite SPA and reverse-proxies the +# API + Socket.IO traffic to the api container on the private network. +upstream tx_api { + server api:8080; +} + +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Health endpoint for compose / load balancer probes. + location = /healthz { + access_log off; + return 200 "ok\n"; + add_header Content-Type text/plain; + } + + # Socket.IO upgrade — must come before the generic /api/ block so the + # WebSocket handshake gets the upgrade headers it needs. + location /api/socket.io/ { + proxy_pass http://tx_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 1h; + } + + # All other API calls. + location /api/ { + proxy_pass http://tx_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 25m; + } + + # SPA: serve hashed assets with long-cache; fall back to index.html. + location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg|ico|webp|map)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a547036a..7375e6ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,15 +6,6 @@ settings: catalogs: default: - '@replit/vite-plugin-cartographer': - specifier: ^0.5.1 - version: 0.5.1 - '@replit/vite-plugin-dev-banner': - specifier: ^0.1.1 - version: 0.1.2 - '@replit/vite-plugin-runtime-error-modal': - specifier: ^0.0.6 - version: 0.0.6 '@tailwindcss/vite': specifier: ^4.1.14 version: 4.2.1 @@ -81,9 +72,12 @@ importers: artifacts/api-server: dependencies: - '@google-cloud/storage': - specifier: ^7.19.0 - version: 7.19.0 + '@aws-sdk/client-s3': + specifier: ^3.658.0 + version: 3.1045.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.658.0 + version: 3.1045.0 '@swc/helpers': specifier: ^0.5.21 version: 0.5.21 @@ -123,9 +117,6 @@ importers: express-session: specifier: ^1.19.0 version: 1.19.0 - google-auth-library: - specifier: ^10.6.2 - version: 10.6.2 nodemailer: specifier: ^8.0.7 version: 8.0.7 @@ -286,12 +277,6 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@replit/vite-plugin-cartographer': - specifier: 'catalog:' - version: 0.5.1 - '@replit/vite-plugin-runtime-error-modal': - specifier: 'catalog:' - version: 0.0.6 '@tailwindcss/vite': specifier: 'catalog:' version: 4.2.1(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -527,15 +512,6 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.0 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@replit/vite-plugin-cartographer': - specifier: 'catalog:' - version: 0.5.1 - '@replit/vite-plugin-dev-banner': - specifier: 'catalog:' - version: 0.1.2 - '@replit/vite-plugin-runtime-error-modal': - specifier: 'catalog:' - version: 0.0.6 '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.19(tailwindcss@4.2.1) @@ -742,6 +718,173 @@ importers: packages: + '@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.1045.0': + resolution: {integrity: sha512-fsuO3Y6t+3Ro9Bsg41DKj4Sfy53CGSrhnMldNplWmG8Tx0UbYk+YDa4RD1hVlJpERw4JBmPkl0+J9qlxMh1pcA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.8': + resolution: {integrity: sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.7': + resolution: {integrity: sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.34': + resolution: {integrity: sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.36': + resolution: {integrity: sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.38': + resolution: {integrity: sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.38': + resolution: {integrity: sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.39': + resolution: {integrity: sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.34': + resolution: {integrity: sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.38': + resolution: {integrity: sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.38': + resolution: {integrity: sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.10': + resolution: {integrity: sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.10': + resolution: {integrity: sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.16': + resolution: {integrity: sha512-6ru8doI0/XzszqLIPXf0E/V7HhAw1Pu94010XCKYtBUfD0LxF0BuOzrUf8OQGR6j2o6wgKTHUniOmndQycHwCA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.10': + resolution: {integrity: sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==} + 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.11': + resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.37': + resolution: {integrity: sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==} + 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.38': + resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.6': + resolution: {integrity: sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.13': + resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1045.0': + resolution: {integrity: sha512-VDRF8GIuUPX+K4DUYrvcODj/h54LOmdJ7DhpLQ0wrYrdxzIiJEpi0n9jZ1bbjT2UxhwTbOorse5EGo+gnOK2aA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.25': + resolution: {integrity: sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1041.0': + resolution: {integrity: sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.8': + resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.8': + resolution: {integrity: sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.10': + resolution: {integrity: sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==} + 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.10': + resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} + + '@aws-sdk/util-user-agent-node@3.973.24': + resolution: {integrity: sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.22': + resolution: {integrity: sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==} + 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'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1336,22 +1479,6 @@ packages: '@gerrit0/mini-shiki@3.23.0': resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} - '@google-cloud/paginator@5.0.2': - resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==} - engines: {node: '>=14.0.0'} - - '@google-cloud/projectify@4.0.0': - resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} - engines: {node: '>=14.0.0'} - - '@google-cloud/promisify@4.0.0': - resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} - engines: {node: '>=14'} - - '@google-cloud/storage@7.19.0': - resolution: {integrity: sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==} - engines: {node: '>=14'} - '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -2098,15 +2225,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@replit/vite-plugin-cartographer@0.5.1': - resolution: {integrity: sha512-KiaQMx4ssTM8hSjMFscyIV1PN1bRVCWCFqN3fXOfXa39uFFCKUaeL3VvH/sxuy7S7ET+MCFOt+J7lLtL56bRrA==} - - '@replit/vite-plugin-dev-banner@0.1.2': - resolution: {integrity: sha512-YfW3U1xKnLrqvSiTzXeEX8AG+Vpz7XwBsJHNvGbp841AE1mLvishMQi2Zw7ApyHp+9EMGthXuCjP+mLbl3IuGA==} - - '@replit/vite-plugin-runtime-error-modal@0.0.6': - resolution: {integrity: sha512-53iuzLsrvcUnWxAo0fvNrUhOf7LYJ+3at61dZeTIrkaZD4vGNjTbvE0j50TFcjjTC9UM74uprlnQ4+L2A//Cjg==} - '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -2294,6 +2412,178 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@smithy/config-resolver@4.5.1': + resolution: {integrity: sha512-abXk3LhODsvRHsk0ZS9ztrg/fZatTa9Z/z4pgx65YSLR+rY6kvUG/1IgcDKEUciR8MfdnkT5oPeHJTy/HhzDIQ==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.24.1': + resolution: {integrity: sha512-3mT7o4qQyUWttYnVK3A0Z/u3Xha3E81tXn32Tz6vjZiUXhBrkEivpw1hBYfh84iFF9CSzkBU9Y1DJ3Q6RQ231g==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.1': + resolution: {integrity: sha512-0S/acwHnqX4WrjXzhdiDRxsG2s9SC0cpPIK9nZ1R6UOHd+j7uL28+4bHu22urbLk2TVw3fkp6na/+fkUt/pLNQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.3.1': + resolution: {integrity: sha512-X7MyI1fu8M84IPKk49kO4kb27Mqp6un9/0o/MsA1ngZ5OxxWKGUxPS3S/AJ9q1cPVTSGmRcbaGNfGUSsflTJkg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.4.1': + resolution: {integrity: sha512-JZGbSXaBk7JY8VPzsh66ksJ0nTWXbApduFDkA/pEl3aTm2EoAiUZE1Iltp6c+X1bB8kxPQW0mHDfVdYCpWTOzg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.3.1': + resolution: {integrity: sha512-6Cn4xTNVxn9PWTHSbvf8zmcDhQW8lrLE1Xq5CJgmX6wEvdjS2S0KuE79Aiznv/jx51jpFJ98OuWyE+Bt+oG1MQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.1': + resolution: {integrity: sha512-r7bN6spQ+caZC8AnyvSxkRUb57zt2jhhRw3Z+2Ez8hjq6coIikDBFUUI/+CQ1xx9K6eX1Gx6wUKo4ylU66TIqw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.3.1': + resolution: {integrity: sha512-2fbltQVQYmGd0OzPv2oDMRF0pxkzeIx8cbpx2x6W3UJWGaEyUzVPxF4d0sDXZ/r2obg+RbTyhTidXWlPDsKRKw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.3.1': + resolution: {integrity: sha512-u0/zo11mg7yNneoYgTkH4sXwSmcBpbl49o4UNCtQ7hYsXxynsN25KYHmXzqi7TPk5HQL5klGnpU5koOY0O+9hw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.3.1': + resolution: {integrity: sha512-4NOnngIoXngbJw9By3u8KXRgqt4vYATpAobNBnNWxOREP7JY3kB0bUmbBNhZ7dtZV/b4auO1eFMD4cLj9OauVg==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.3.1': + resolution: {integrity: sha512-cLmwtDoulyZvRepAfyV+3rx5oMvuh51dbE+6En3vGC09j3uVSRt1U4oguNu32ub3soGX0oYtBs8E7S2Q4SxTqg==} + 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/is-array-buffer@4.3.1': + resolution: {integrity: sha512-9aVG6VjOFVFHC6Z4hGAzIIrsVWpp1QOO4ERQ2k1S19VrgCamUGIBE2ilAnMWCfr+mlowHlLRXBStsTk/2c5HfA==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.3.1': + resolution: {integrity: sha512-98NalujRdzv6ggVQNYPWpL2K57UKeUB8roIr61u6+JiHd7KUlMQ+sn/vk6IG4XxEjw2vlC7eu/xjYXshUE4XXg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.3.1': + resolution: {integrity: sha512-l4BUIP+wljW/Ar+0/QcGdmElI9lalrywfzNijXMBG34Z510FRzPyrDLx/blNTZOAm0C4Mvx5t/bf760CZo1ajg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.5.1': + resolution: {integrity: sha512-qtqu5TS+8Y18ZDkJoiXN5AMW1G4JAg1+xytzpsUvIR5a4EUsgd5HQg12lekEHWpm2TDUmOgg+hBaHK7dvyWdkA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.6.1': + resolution: {integrity: sha512-eTaQhxs0rfUuAkL2MSKrH8DTO7YCeAgrdN0B2/RAeuHmXQ+x52dk5qUBsi/jtcqe5LxItgq5AG5tI6Cp8c0sow==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.3.1': + resolution: {integrity: sha512-t7YtUe076zWVypVmy1rX91oKi2TFJCkpfFpfMhJFpEIRPP0iL9JxjeSyFQ+1bF45JUfDzOzslUJa150WcSrBug==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.3.1': + resolution: {integrity: sha512-1jKwiKZxCMQNqmp4uVPYA6r+MLGjEtH07gnOUdPgbnjuOIrl/0JY/ICdpQtFgeBsQ/Up01gnSv8GYEL0fb8yvg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.4.1': + resolution: {integrity: sha512-q7tDJEJXcaSG/8TVpu2f2l9bzxTzDM9geWmltbzsY6Hfh3yiuXXTpLIO8+zwYASPPVFaTJpdKwjSSjdoDoccgw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.7.1': + resolution: {integrity: sha512-BdEYko85f/ldp68uH8XEyIvo810xFk6eyPH81SRggTOApYHWA+Xu7B2EzLuHbe37WVLaUA7F1fWR3/zBeme2WA==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.3.1': + resolution: {integrity: sha512-3NHoqVBhzpY2b4YBx9AqyKC4C8nnEjl5FyKuxrCjvnjinG0ODj+yg1xX360nNahT6wghYjSw1SooCt3kIdnqIA==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.4.1': + resolution: {integrity: sha512-8irPNCQgYxcSFp1aGcnDNFkTwSA+xPUaFq9V/v1+JXWu8sKr5b3cFmg2kBTkjkvypDmGeNffuNu0x5iqw1NoAw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.3.1': + resolution: {integrity: sha512-toyi8sXPWDNoVH6yK7sXJ9dm5uxw2tWLCHzPy/t16Fvl62Es4vXQXzlilyNaw+DqFwxSlrFClh0rGLPUF2p9Lg==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.5.1': + resolution: {integrity: sha512-FKoKxVzdFPhyynFI+SPTWrgOP60fZ4l1UwukWYj4eyhpSmEI7MJ6p58hawIIt9bwp+aek9NEm8Zika7E+GEoeg==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.1': + resolution: {integrity: sha512-728lZZEWYWubBESrfntNslZQYDKRlJDY4dcDnYbL50+gu35pGPLblu4S0/RH/RDLF6me1M87ECHsHELGL7dA/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.13.1': + resolution: {integrity: sha512-IcznNM8Qd9u1X3oflp12tkzyOB4HbT+sfYWlWiyEysgNzSHoWcHUUsTT4y1jjDjtVuuVVQbYks+g1kVd7u1eGQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.1': + resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.3.1': + resolution: {integrity: sha512-tuelFlF2PZR/wogFC58NIrPOv+Zna4N1+3kA161/33D1Gbwvl6Nh4WsAsW05ZyPp0O6CMGsdbb0S2b/qVjRMCw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.4.1': + resolution: {integrity: sha512-fTHiwW2xbiRiWzfSk4IGAr3gNZCH4fuRYqt8+IuarsP/YON35576iVdePraZ6yJlFxlCL0eMec3/F7xYqoKzlg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.3.1': + resolution: {integrity: sha512-1scg5t4nV3hV7CZs996/XHb80aDZ5YotH4NcvkW/w/rHj+cSz0aCIzwz8aUNKB4nCDPSHRCbrKoj+TvycYefmw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.3.1': + resolution: {integrity: sha512-VRC8MKVPKrgUYThTA7ughcKMfjW6/X92H0wXGJoda0Apw4O5xbXL0GMLz40DTWlsb5hh2iItk6+XL72uJdxYcw==} + 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-config-provider@4.3.1': + resolution: {integrity: sha512-lw6L5GF5+W19vO6o3fZwRT2cXEG+8b2LH0b9ppjDT6nIxjUgmljEQGninx5XorylwKZZ4XLVABeroJ8oaF9RmQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.4.1': + resolution: {integrity: sha512-1rA7w+LjK1WJClsffC81Z/ZtjFt22QsKhBjUYEnZsGVS2nOTfOENKBzdg4SxhdwFvBCjcbpjscUfXOPwE3UHWQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.3.1': + resolution: {integrity: sha512-1fk1wfQHBenQD5NitVKOFgW0wsISYAFPIXGyStJWAeCtMyRhgHYvtJxBk2rwGWA0L5QX6oM6yeHSLKPFMk59ww==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.5.1': + resolution: {integrity: sha512-yORYzJD5zoGbSDkAACr0dIjDiSEA3X8h8lggDENl1dkKpCG0TQIoItPBqtvuJHzFFjRXumcoH+/09xIuixGyCw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.3.1': + resolution: {integrity: sha512-SRRMDcIgVXVhVbxviBaSZbuWuVW3jD08wv4ESV0V2oiw0Mki8TPVQ5IxwD3MvSTPg52QYsRP+JoMw5WdUdeWAg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.4.1': + resolution: {integrity: sha512-qkgWgwn1xw0GoY9Ea/B6FrYSPfHA0zyOtJkokwxZuvucRf2+2lfTut6adi4e4Y7LEAaxsFG7r6i05mtDCxbHKA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.6.1': + resolution: {integrity: sha512-GjZfEft0M0V3n2YM/LGkr5LeLd8gxHUIzW0rUz6VtTtlAq245GxHlJghvoPEjJHKTj255iHFAiA4IsIdK40Ueg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.3.1': + resolution: {integrity: sha512-FtRrSnriXtOs4+J8/y9SbQ1xmN71hrOsN/YJr5PQQj5nR1l7YNkGS/TEk4gr0WN7gyrUqw8/RFaYVjI18732ZA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.4.1': + resolution: {integrity: sha512-G/gWDykZNL0NVcd1qXkoKm45jxJECp6q53DSomM5QKMsyAMEsGksVq+HwgonqYxfFJEzzHi6ljtWKXVS1pl0/Q==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -2580,10 +2870,6 @@ packages: '@tiptap/starter-kit@3.22.4': resolution: {integrity: sha512-qWjw+vfdin1rzMRpRU4cC5tLTwMJtUpXeQukv+6mOqqvhptuwuZBjUHImVEJaSPoHXS7+1ut+nTnrLyWyEuE5Q==} - '@tootallnate/once@2.0.0': - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - '@transloadit/prettier-bytes@0.3.5': resolution: {integrity: sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==} @@ -2606,9 +2892,6 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - '@types/caseless@0.12.5': - resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} - '@types/connect-pg-simple@7.0.3': resolution: {integrity: sha512-NGCy9WBlW2bw+J/QlLnFZ9WjoGs6tMo3LAut6mY4kK+XHzue//lpNVpAvYRpIwM969vBRAM2Re0izUvV6kt+NA==} @@ -2698,9 +2981,6 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/request@2.48.13': - resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} - '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -2713,9 +2993,6 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/tough-cookie@4.0.5': - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -2803,10 +3080,6 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2820,14 +3093,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -2860,16 +3125,6 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} - arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} - - async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -2900,13 +3155,13 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -2925,9 +3180,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2977,10 +3229,6 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -3081,10 +3329,6 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -3121,10 +3365,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -3268,12 +3508,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexify@4.1.3: - resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -3339,10 +3573,6 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - esbuild-plugin-pino@2.3.3: resolution: {integrity: sha512-5RIsILwgqy8wIV5pVg2gb13gJlH3EKKg613Js8q25p3tFsKA8ftsgWQFdgGbIkUe77Ttjl8lctuGkchRAvXGfw==} peerDependencies: @@ -3395,10 +3625,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -3426,9 +3652,6 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-copy@4.0.2: resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} @@ -3452,8 +3675,8 @@ packages: fast-xml-builder@1.1.5: resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==} - fast-xml-parser@5.7.1: - resolution: {integrity: sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==} + fast-xml-parser@5.7.2: + resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} hasBin: true fastq@1.20.1: @@ -3468,10 +3691,6 @@ packages: picomatch: optional: true - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -3491,14 +3710,6 @@ packages: fontkit@2.0.4: resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} - form-data@2.5.5: - resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} - engines: {node: '>= 0.12'} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -3538,22 +3749,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gaxios@6.7.1: - resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} - engines: {node: '>=14'} - - gaxios@7.1.4: - resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==} - engines: {node: '>=18'} - - gcp-metadata@6.1.1: - resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} - engines: {node: '>=14'} - - gcp-metadata@8.1.2: - resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} - engines: {node: '>=18'} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -3589,22 +3784,6 @@ packages: resolution: {integrity: sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==} engines: {node: '>=20'} - google-auth-library@10.6.2: - resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} - engines: {node: '>=18'} - - google-auth-library@9.15.1: - resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} - engines: {node: '>=14'} - - google-logging-utils@0.0.2: - resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} - engines: {node: '>=14'} - - google-logging-utils@1.1.3: - resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} - engines: {node: '>=14'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3612,18 +3791,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - gtoken@7.1.0: - resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} - engines: {node: '>=14.0.0'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -3631,9 +3802,6 @@ packages: help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - html-entities@2.6.0: - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} @@ -3644,18 +3812,6 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - human-signals@8.0.1: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} @@ -3728,10 +3884,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -3766,9 +3918,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-bigint@1.0.0: - resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -3784,12 +3933,6 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} - jwa@2.0.1: - resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - - jws@4.0.1: - resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - leven@4.1.0: resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3948,11 +4091,6 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - minimatch@9.0.9: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} @@ -3963,9 +4101,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - modern-screenshot@4.6.8: - resolution: {integrity: sha512-GJkv/yWPOJTlxj1LZDU2k474cDyOWL+LVaqTdDWQwQ5d8zIuTz1892+1cV9V0ZpK6HYZFo/+BNLBbierO9d2TA==} - motion-dom@12.35.1: resolution: {integrity: sha512-7n6r7TtNOsH2UFSAXzTkfzOeO5616v9B178qBIjmu/WgEyJK0uqwytCEhwKBTuM/HJA40ptAw7hLFpxtPAMRZQ==} @@ -4005,24 +4140,6 @@ packages: react: 19.1.0 react-dom: 19.1.0 - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} @@ -4070,10 +4187,6 @@ packages: prettier: optional: true - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4425,10 +4538,6 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -4468,10 +4577,6 @@ packages: restructure@3.0.2: resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} - retry-request@7.0.2: - resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==} - engines: {node: '>=14'} - retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -4607,19 +4712,10 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stream-events@1.0.5: - resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} - - stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4635,9 +4731,6 @@ packages: strnum@2.2.3: resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} - stubs@3.0.0: - resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} - tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -4653,10 +4746,6 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - teeny-request@9.0.0: - resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} - engines: {node: '>=14'} - thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} @@ -4678,9 +4767,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -4796,14 +4882,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -4864,16 +4942,6 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -4918,10 +4986,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -4938,6 +5002,471 @@ packages: snapshots: + '@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.1045.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.8 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws-sdk/middleware-bucket-endpoint': 3.972.10 + '@aws-sdk/middleware-expect-continue': 3.972.10 + '@aws-sdk/middleware-flexible-checksums': 3.974.16 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-location-constraint': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-sdk-s3': 3.972.37 + '@aws-sdk/middleware-ssec': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.25 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.5.1 + '@smithy/core': 3.24.1 + '@smithy/eventstream-serde-browser': 4.3.1 + '@smithy/eventstream-serde-config-resolver': 4.4.1 + '@smithy/eventstream-serde-node': 4.3.1 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/hash-blob-browser': 4.3.1 + '@smithy/hash-node': 4.3.1 + '@smithy/hash-stream-node': 4.3.1 + '@smithy/invalid-dependency': 4.3.1 + '@smithy/md5-js': 4.3.1 + '@smithy/middleware-content-length': 4.3.1 + '@smithy/middleware-endpoint': 4.5.1 + '@smithy/middleware-retry': 4.6.1 + '@smithy/middleware-serde': 4.3.1 + '@smithy/middleware-stack': 4.3.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-body-length-browser': 4.3.1 + '@smithy/util-body-length-node': 4.3.1 + '@smithy/util-defaults-mode-browser': 4.4.1 + '@smithy/util-defaults-mode-node': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-stream': 4.6.1 + '@smithy/util-utf8': 4.3.1 + '@smithy/util-waiter': 4.4.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.974.8': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/xml-builder': 3.972.22 + '@smithy/core': 3.24.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.7': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.34': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-stream': 4.6.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-login': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.3.1 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.39': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-ini': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.3.1 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.34': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/token-providers': 3.1041.0 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.4.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.16': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/crc64-nvme': 3.972.7 + '@aws-sdk/types': 3.973.8 + '@smithy/is-array-buffer': 4.3.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-stream': 4.6.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.37': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.24.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.3.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-stream': 4.6.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@smithy/core': 3.24.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-retry': 4.4.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.6': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.25 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.5.1 + '@smithy/core': 3.24.1 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/hash-node': 4.3.1 + '@smithy/invalid-dependency': 4.3.1 + '@smithy/middleware-content-length': 4.3.1 + '@smithy/middleware-endpoint': 4.5.1 + '@smithy/middleware-retry': 4.6.1 + '@smithy/middleware-serde': 4.3.1 + '@smithy/middleware-stack': 4.3.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-body-length-browser': 4.3.1 + '@smithy/util-body-length-node': 4.3.1 + '@smithy/util-defaults-mode-browser': 4.4.1 + '@smithy/util-defaults-mode-node': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.13': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/config-resolver': 4.5.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1045.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.25 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-format-url': 3.972.10 + '@smithy/middleware-endpoint': 4.5.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.25': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.37 + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1041.0': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.8': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.8': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/querystring-builder': 4.3.1 + '@smithy/types': 4.14.1 + 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.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.24': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/types': 3.973.8 + '@smithy/node-config-provider': 4.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.22': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.1 + fast-xml-parser: 5.7.2 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -5344,36 +5873,6 @@ snapshots: '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - '@google-cloud/paginator@5.0.2': - dependencies: - arrify: 2.0.1 - extend: 3.0.2 - - '@google-cloud/projectify@4.0.0': {} - - '@google-cloud/promisify@4.0.0': {} - - '@google-cloud/storage@7.19.0': - dependencies: - '@google-cloud/paginator': 5.0.2 - '@google-cloud/projectify': 4.0.0 - '@google-cloud/promisify': 4.0.0 - abort-controller: 3.0.0 - async-retry: 1.3.3 - duplexify: 4.1.3 - fast-xml-parser: 5.7.1 - gaxios: 6.7.1 - google-auth-library: 9.15.1 - html-entities: 2.6.0 - mime: 3.0.0 - p-limit: 3.1.0 - retry-request: 7.0.2 - teeny-request: 9.0.0 - uuid: 8.3.2 - transitivePeerDependencies: - - encoding - - supports-color - '@hookform/resolvers@3.10.0(react-hook-form@7.71.2(react@19.1.0))': dependencies: react-hook-form: 7.71.2(react@19.1.0) @@ -6224,22 +6723,6 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@replit/vite-plugin-cartographer@0.5.1': - dependencies: - '@babel/parser': 7.29.0 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - magic-string: 0.30.21 - modern-screenshot: 4.6.8 - transitivePeerDependencies: - - supports-color - - '@replit/vite-plugin-dev-banner@0.1.2': {} - - '@replit/vite-plugin-runtime-error-modal@0.0.6': - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/rollup-android-arm-eabi@4.59.0': @@ -6374,6 +6857,225 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/config-resolver@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/core@3.24.1': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/hash-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/md5-js@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.6.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/property-provider@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/protocol-http@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/shared-ini-file-loader@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/smithy-client@4.13.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/types@4.14.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-base64@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + 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-config-provider@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-middleware@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-retry@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-stream@4.6.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-waiter@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} '@swc/helpers@0.5.21': @@ -6649,8 +7351,6 @@ snapshots: '@tiptap/extensions': 3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4) '@tiptap/pm': 3.22.4 - '@tootallnate/once@2.0.0': {} - '@transloadit/prettier-bytes@0.3.5': {} '@types/babel__core@7.20.5': @@ -6683,8 +7383,6 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 25.3.5 - '@types/caseless@0.12.5': {} - '@types/connect-pg-simple@7.0.3': dependencies: '@types/express': 5.0.6 @@ -6786,13 +7484,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/request@2.48.13': - dependencies: - '@types/caseless': 0.12.5 - '@types/node': 25.3.5 - '@types/tough-cookie': 4.0.5 - form-data: 2.5.5 - '@types/retry@0.12.2': {} '@types/sanitize-html@2.16.1': @@ -6808,8 +7499,6 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.3.5 - '@types/tough-cookie@4.0.5': {} - '@types/trusted-types@2.0.7': optional: true @@ -6914,10 +7603,6 @@ snapshots: transitivePeerDependencies: - supports-color - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6930,14 +7615,6 @@ snapshots: acorn@8.16.0: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - agent-base@7.1.4: {} - ajv-draft-04@1.0.0(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -6965,14 +7642,6 @@ snapshots: dependencies: tslib: 2.8.1 - arrify@2.0.1: {} - - async-retry@1.3.3: - dependencies: - retry: 0.13.1 - - asynckit@0.4.0: {} - atomic-sleep@1.0.0: {} balanced-match@1.0.2: {} @@ -6991,8 +7660,6 @@ snapshots: dependencies: require-from-string: 2.0.2 - bignumber.js@9.3.1: {} - body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -7007,6 +7674,8 @@ snapshots: transitivePeerDependencies: - supports-color + bowser@2.14.1: {} + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -7031,8 +7700,6 @@ snapshots: node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) - buffer-equal-constant-time@1.0.1: {} - buffer-from@1.1.2: {} bytes@3.1.2: {} @@ -7081,10 +7748,6 @@ snapshots: colorette@2.0.20: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commander@14.0.3: {} compare-versions@6.1.1: {} @@ -7167,8 +7830,6 @@ snapshots: d3-timer@3.0.1: {} - data-uri-to-buffer@4.0.1: {} - date-fns-jalali@4.1.0-0: {} date-fns@3.6.0: {} @@ -7189,8 +7850,6 @@ snapshots: deepmerge@4.3.1: {} - delayed-stream@1.0.0: {} - depd@2.0.0: {} dequal@2.0.3: {} @@ -7253,17 +7912,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexify@4.1.3: - dependencies: - end-of-stream: 1.4.5 - inherits: 2.0.4 - readable-stream: 3.6.2 - stream-shift: 1.0.3 - - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 - ee-first@1.1.1: {} electron-to-chromium@1.5.307: {} @@ -7339,13 +7987,6 @@ snapshots: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - esbuild-plugin-pino@2.3.3(esbuild@0.27.3)(pino-pretty@13.1.3)(pino@9.14.0)(thread-stream@3.1.0): dependencies: esbuild: 0.27.3 @@ -7454,8 +8095,6 @@ snapshots: etag@1.8.1: {} - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} eventemitter3@5.0.4: {} @@ -7528,8 +8167,6 @@ snapshots: transitivePeerDependencies: - supports-color - extend@3.0.2: {} - fast-copy@4.0.2: {} fast-deep-equal@3.1.3: {} @@ -7552,7 +8189,7 @@ snapshots: dependencies: path-expression-matcher: 1.5.0 - fast-xml-parser@5.7.1: + fast-xml-parser@5.7.2: dependencies: '@nodable/entities': 2.1.0 fast-xml-builder: 1.1.5 @@ -7567,11 +8204,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -7608,19 +8240,6 @@ snapshots: unicode-properties: 1.4.1 unicode-trie: 2.0.0 - form-data@2.5.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - safe-buffer: 5.2.1 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - forwarded@0.2.0: {} framer-motion@12.35.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -7648,42 +8267,6 @@ snapshots: function-bind@1.1.2: {} - gaxios@6.7.1: - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - is-stream: 2.0.1 - node-fetch: 2.7.0 - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - supports-color - - gaxios@7.1.4: - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - node-fetch: 3.3.2 - transitivePeerDependencies: - - supports-color - - gcp-metadata@6.1.1: - dependencies: - gaxios: 6.7.1 - google-logging-utils: 0.0.2 - json-bigint: 1.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - gcp-metadata@8.1.2: - dependencies: - gaxios: 7.1.4 - google-logging-utils: 1.1.3 - json-bigint: 1.0.0 - transitivePeerDependencies: - - supports-color - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -7730,59 +8313,18 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.4.0 - google-auth-library@10.6.2: - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 7.1.4 - gcp-metadata: 8.1.2 - google-logging-utils: 1.1.3 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - - google-auth-library@9.15.1: - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1 - gcp-metadata: 6.1.1 - gtoken: 7.1.0 - jws: 4.0.1 - transitivePeerDependencies: - - encoding - - supports-color - - google-logging-utils@0.0.2: {} - - google-logging-utils@1.1.3: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} - gtoken@7.1.0: - dependencies: - gaxios: 6.7.1 - jws: 4.0.1 - transitivePeerDependencies: - - encoding - - supports-color - has-symbols@1.1.0: {} - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - hasown@2.0.2: dependencies: function-bind: 1.1.2 help-me@5.0.0: {} - html-entities@2.6.0: {} - html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 @@ -7802,28 +8344,6 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@5.0.0: - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - human-signals@8.0.1: {} i18next@26.0.6(typescript@5.9.3): @@ -7869,8 +8389,6 @@ snapshots: is-promise@4.0.0: {} - is-stream@2.0.1: {} - is-stream@4.0.1: {} is-unicode-supported@2.1.0: {} @@ -7891,10 +8409,6 @@ snapshots: jsesc@3.1.0: {} - json-bigint@1.0.0: - dependencies: - bignumber.js: 9.3.1 - json-schema-traverse@1.0.0: {} json5@2.2.3: {} @@ -7907,17 +8421,6 @@ snapshots: jsonpointer@5.0.1: {} - jwa@2.0.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@4.0.1: - dependencies: - jwa: 2.0.1 - safe-buffer: 5.2.1 - leven@4.1.0: {} lightningcss-android-arm64@1.31.1: @@ -8044,8 +8547,6 @@ snapshots: dependencies: mime-db: 1.54.0 - mime@3.0.0: {} - minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 @@ -8054,8 +8555,6 @@ snapshots: mitt@3.0.1: {} - modern-screenshot@4.6.8: {} - motion-dom@12.35.1: dependencies: motion-utils: 12.29.2 @@ -8081,18 +8580,6 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - node-domexception@1.0.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - node-releases@2.0.36: {} nodemailer@8.0.7: {} @@ -8158,10 +8645,6 @@ snapshots: - supports-color - typescript - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-limit@4.0.0: dependencies: yocto-queue: 1.2.2 @@ -8540,12 +9023,6 @@ snapshots: react@19.1.0: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -8579,15 +9056,6 @@ snapshots: restructure@3.0.2: {} - retry-request@7.0.2: - dependencies: - '@types/request': 2.48.13 - extend: 3.0.2 - teeny-request: 9.0.0 - transitivePeerDependencies: - - encoding - - supports-color - retry@0.13.1: {} reusify@1.1.0: {} @@ -8790,18 +9258,8 @@ snapshots: statuses@2.0.2: {} - stream-events@1.0.5: - dependencies: - stubs: 3.0.0 - - stream-shift@1.0.3: {} - string-argv@0.3.2: {} - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -8812,8 +9270,6 @@ snapshots: strnum@2.2.3: {} - stubs@3.0.0: {} - tailwind-merge@3.5.0: {} tailwindcss-animate@1.0.7(tailwindcss@4.2.1): @@ -8824,17 +9280,6 @@ snapshots: tapable@2.3.0: {} - teeny-request@9.0.0: - dependencies: - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - node-fetch: 2.7.0 - stream-events: 1.0.5 - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - supports-color - thread-stream@3.1.0: dependencies: real-require: 0.2.0 @@ -8854,8 +9299,6 @@ snapshots: toidentifier@1.0.1: {} - tr46@0.0.3: {} - tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -8949,10 +9392,6 @@ snapshots: util-deprecate@1.0.2: {} - uuid@8.3.2: {} - - uuid@9.0.1: {} - vary@1.1.2: {} vaul@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -9001,15 +9440,6 @@ snapshots: w3c-keyname@2.2.8: {} - web-streams-polyfill@3.3.3: {} - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -9035,8 +9465,6 @@ snapshots: yaml@2.8.2: {} - yocto-queue@0.1.0: {} - yocto-queue@1.2.2: {} yoctocolors@2.1.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 332ec57b..9c01c34a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,9 +7,6 @@ packages: autoInstallPeers: false catalog: - '@replit/vite-plugin-cartographer': ^0.5.1 - '@replit/vite-plugin-dev-banner': ^0.1.1 - '@replit/vite-plugin-runtime-error-modal': ^0.0.6 '@tailwindcss/vite': ^4.1.14 '@tanstack/react-query': ^5.90.21 '@types/node': ^25.3.3 @@ -31,9 +28,7 @@ catalog: minimumReleaseAge: 1440 -minimumReleaseAgeExclude: - - '@replit/*' - - stripe-replit-sync +minimumReleaseAgeExclude: [] onlyBuiltDependencies: - '@swc/core' diff --git a/scripts/src/seed.ts b/scripts/src/seed.ts index d01d6a10..b9bf47a8 100644 --- a/scripts/src/seed.ts +++ b/scripts/src/seed.ts @@ -89,8 +89,20 @@ async function main() { } } - // Create admin user - const adminHash = await bcrypt.hash("admin123", 10); + // Create admin user. Passwords are read from env vars so seeded + // credentials never need to be committed; the dev-only fallbacks are + // safe for fresh local installs but unsafe in production. + const adminPassword = + process.env.SEED_ADMIN_PASSWORD ?? + (process.env.NODE_ENV === "production" + ? (() => { throw new Error("SEED_ADMIN_PASSWORD must be set in production"); })() + : "admin123"); + const userPassword = + process.env.SEED_USER_PASSWORD ?? + (process.env.NODE_ENV === "production" + ? (() => { throw new Error("SEED_USER_PASSWORD must be set in production"); })() + : "user123"); + const adminHash = await bcrypt.hash(adminPassword, 10); const [adminUser] = await db .insert(usersTable) .values({ @@ -106,7 +118,7 @@ async function main() { .returning(); // Create regular user - const userHash = await bcrypt.hash("user123", 10); + const userHash = await bcrypt.hash(userPassword, 10); const [regularUser] = await db .insert(usersTable) .values({ @@ -854,8 +866,8 @@ async function main() { console.log("\n✅ Seeding complete!"); console.log("\nDemo accounts:"); - console.log(" Admin: username=admin, password=admin123"); - console.log(" User: username=ahmed, password=user123"); + console.log(` Admin: username=admin, password=${adminPassword}`); + console.log(` User: username=ahmed, password=${userPassword}`); process.exit(0); } diff --git a/sedMkjeJm b/sedMkjeJm deleted file mode 100644 index fd59a2dc..00000000 --- a/sedMkjeJm +++ /dev/null @@ -1,71 +0,0 @@ -modules = ["nodejs-24", "postgresql-16"] - -[deployment] -router = "application" -deploymentTarget = "autoscale" - -[deployment.postBuild] -args = ["pnpm", "store", "prune"] -env = { "CI" = "true" } - -[workflows] -runButton = "Project" - -[[workflows.workflow]] -name = "Project" -mode = "parallel" -author = "agent" - -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "test" - -[[workflows.workflow]] -name = "test" -author = "agent" - -[[workflows.workflow.tasks]] -task = "shell.exec" -args = "pnpm --filter @workspace/api-server test && pnpm --filter @workspace/tx-os test:e2e" - -[workflows.workflow.metadata] -isValidation = true - -[agent] -stack = "PNPM_WORKSPACE" -expertMode = true - -[postMerge] -path = "scripts/post-merge.sh" -timeoutMs = 120000 - -[[ports]] -localPort = 8080 -externalPort = 8080 - -[[ports]] -localPort = 8081 -externalPort = 80 - -[[ports]] -localPort = 8082 -externalPort = 3003 - -[[ports]] -localPort = 8083 -externalPort = 5173 - -[[ports]] -localPort = 25785 -externalPort = 3000 - -[[ports]] -localPort = 25786 -externalPort = 3002 - -[[ports]] -localPort = 25787 -externalPort = 5000 - -[nix] -channel = "stable-25_05" diff --git a/threat_model.md b/threat_model.md index 0c2ef72a..a987e825 100644 --- a/threat_model.md +++ b/threat_model.md @@ -10,7 +10,7 @@ ## 1. System overview -Tx OS is an internal "office OS" for a single-tenant organization. It is deployed as a Replit app (single Express + Postgres + Object Storage stack) with a React/Vite SPA, a small mobile-friendly UI, and a real-time notification layer over Socket.IO. The app is bilingual (Arabic / English), and the workforce uses it to: +Tx OS is an internal "office OS" for a single-tenant organization. It is deployed as a self-hosted container stack (Express API + Postgres + S3-compatible object storage such as MinIO) with a React/Vite SPA, a small mobile-friendly UI, and a real-time notification layer over Socket.IO. The app is bilingual (Arabic / English), and the workforce uses it to: - Order internal services (e.g. coffee, printing). - Manage notes, including shared-folder collaboration with checklist items. @@ -46,8 +46,8 @@ The app is **single-tenant** — there is no multi-tenant isolation requirement; │ Public internet │ │ │ │ ┌────────────┐ HTTPS ┌──────────────────────────────────┐ │ -│ │ Browser │ ─────────▶ │ Replit edge (mTLS-terminated │ │ -│ │ (SPA) │ │ proxy, deploys.replit.app) │ │ +│ │ Browser │ ─────────▶ │ TLS-terminating reverse proxy │ │ +│ │ (SPA) │ │ (Caddy / Nginx / Traefik) │ │ │ └────────────┘ └──────────────┬──────────────────┘ │ │ │ │ └────────────────────────────────────────────┼───────────────────────┘ @@ -65,18 +65,18 @@ The app is **single-tenant** — there is no multi-tenant isolation requirement; │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌──────────────────┐ ┌───────────────┐ - │ PostgreSQL │ │ Replit Object │ │ Replit sidecar│ - │ (Replit- │ │ Storage (GCS via │ │ http://127. │ - │ managed) │ │ signed URLs) │ │ 0.0.1:1106 │ + │ PostgreSQL │ │ MinIO / S3 │ │ Local FS │ + │ (container) │ │ (signed URLs, │ │ (dev fallback │ + │ │ │ private network)│ │ driver only) │ └───────────────┘ └──────────────────┘ └───────────────┘ ``` **Trust boundaries (numbered):** -1. **Browser ↔ Replit edge.** TLS-terminated by the platform proxy. The SPA is the only first-party client. -2. **Edge ↔ API server.** Loopback within the Replit container. Trust is established via the upstream proxy's mTLS. The API server treats `X-Forwarded-For` as trusted (`app.set("trust proxy", 1)`). +1. **Browser ↔ Edge proxy.** TLS-terminated by an operator-controlled reverse proxy (Caddy / Nginx / Traefik). The SPA is the only first-party client. +2. **Edge ↔ API server.** Loopback or private docker network. The API server treats `X-Forwarded-For` as trusted (`app.set("trust proxy", 1)`); the operator MUST ensure only the edge proxy can reach the API port. 3. **API server ↔ Postgres.** Connection over the in-cluster network; credentials in `DATABASE_URL`. Drizzle ORM is the only query path. -4. **API server ↔ Object Storage.** Signed-URL pattern via the Replit sidecar at `http://127.0.0.1:1106` (loopback HTTP — see Task #522 SAST suppression). All upload URLs are short-lived (15 min) and target `randomUUID()` paths under `PRIVATE_OBJECT_DIR`. +4. **API server ↔ Object Storage.** Signed-URL pattern. In production the API server signs S3 PUT URLs against a self-hosted MinIO endpoint over the private docker network; in local development without S3 the API signs HMAC tokens validated by its own `/api/storage/_local/upload` route, persisting to the local filesystem under `./storage/`. All upload URLs are short-lived (15 min) and target `randomUUID()` paths under `PRIVATE_OBJECT_DIR`. 5. **API server ↔ Socket.IO clients.** Same origin as the API server. Auth shared via session cookie; every socket joins exactly its `user:${userId}` room. --- @@ -163,7 +163,7 @@ For each STRIDE category, the table lists the most consequential threats, the re ## 6. What is explicitly out of scope - **Multi-tenant isolation.** Tx OS is single-tenant. There is no per-tenant data partition. -- **Data residency.** Production data is stored wherever Replit hosts the project; no regional commitments. +- **Data residency.** Production data is stored wherever the operator deploys the docker-compose stack (private VPS or on-prem). The operator chooses the region and is responsible for any regulatory commitments. - **Penetration testing of the live deployment.** This document is design-level only. - **Compliance frameworks** (SOC 2 / ISO 27001). Not in scope today; the threat model is structured so the most material gaps would map cleanly into a future compliance effort if one is opened.