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/MIGRATION_REPORT.md b/MIGRATION_REPORT.md new file mode 100644 index 00000000..291cef99 --- /dev/null +++ b/MIGRATION_REPORT.md @@ -0,0 +1,147 @@ +# Off-Replit Migration Report + +**Task #526** — full migration of Tx OS off the Replit hosted environment to a +self-contained Docker Compose stack runnable on any Linux VPS. + +## Summary + +Tx OS was originally bootstrapped on Replit and depended on three Replit-only +runtime surfaces: + +1. The Replit object-storage **sidecar** (`http://127.0.0.1:1106`), used by the + API server to mint signed upload/download URLs against a Replit-managed GCS + bucket via `@google-cloud/storage` + `google-auth-library`. +2. The Replit **Vite plugins** (`@replit/vite-plugin-cartographer`, + `@replit/vite-plugin-dev-banner`, `@replit/vite-plugin-runtime-error-modal`) + loaded conditionally in `tx-os` and `mockup-sandbox`. +3. The Replit **platform configs** (`.replit`, `replit.nix`, `replit.md`) and + the post-merge hook script (`scripts/post-merge.sh`) consumed by the Replit + agent runner. + +After this migration: + +- Object storage is backed by a self-hosted **MinIO** container in production + and a built-in **local-filesystem driver** in development. Both are abstracted + behind a `StoredObject` interface in `lib/objectStorage.ts` so the route + layer is driver-agnostic. +- Vite plugins are gone from both `vite.config.ts` files and from + `pnpm-workspace.yaml`'s catalog. +- A `Dockerfile` (5 build targets — deps / build / api / web / migrate) and a + `docker-compose.yml` (db + minio + minio-init + api + web + one-shot migrate) + bring the entire stack up with `docker compose up -d`. A new `.env.example` + template documents every env var the runtime reads. + +The repository is now self-contained: cloning it onto a Docker-equipped host +and running the documented commands produces a working deployment. + +## Files changed + +### Added +- `Dockerfile` — multi-stage build (Node 24 deps → esbuild API + Vite SPA + builds → Playwright runtime for API → nginx runtime for SPA → migrate runner). +- `docker-compose.yml` — full production stack with healthchecks and one-shot + init/migrate containers. +- `docker/nginx.conf` — SPA static serve + `/api` and `/api/socket.io` reverse + proxy to the api container. +- `.env.example` — exhaustive, commented configuration template. +- `README.md` — replaces `replit.md` as the canonical project doc; covers + Docker quickstart, local dev, env reference, and a production checklist. +- `MIGRATION_REPORT.md` — this file. +- `artifacts/api-server/src/routes/storage-local-upload.ts` — handler for the + local-FS driver's HMAC-signed PUT upload URLs (`PUT /api/storage/_local/upload`). + +### Replaced (full rewrite) +- `artifacts/api-server/src/lib/objectStorage.ts` — was a thin wrapper around + `@google-cloud/storage` + the Replit sidecar. Now ships a driver interface + with two implementations (`LocalDriver`, `S3Driver`) selected by the + `STORAGE_DRIVER` env var (defaulting to `s3` if `S3_ENDPOINT` is set, else + `local`). Public API surface — `ObjectStorageService.{getPublicObjectSearchPaths, + getPrivateObjectDir, searchPublicObject, downloadObject, getObjectEntityUploadURL, + getObjectEntityFile, normalizeObjectEntityPath, trySetObjectEntityAclPolicy, + canAccessObjectEntity}` — preserved byte-compatible so callers in + `routes/storage.ts` and `routes/executive-meetings.ts` did not need to change. +- `artifacts/api-server/src/lib/objectAcl.ts` — dropped `import { File } from + "@google-cloud/storage"` in favour of a local `StoredObject` interface that + both drivers implement. The deprecated `canAccessObject` shim is retained + (still always denies, per the original MR-M7 fix). +- `artifacts/tx-os/vite.config.ts` and `artifacts/mockup-sandbox/vite.config.ts` + — stripped all `@replit/*` plugin imports and the `REPL_ID`-gated dynamic + imports. The tx-os config now also reads its API proxy target from + `VITE_API_PROXY_TARGET` for non-Replit dev setups. +- `.gitignore` — restructured so that Replit configs (`.replit`, `replit.nix`, + `replit.md`), agent state (`.local/`, `.canvas/`, `.agents/`, `.cache/`, + `.config/`, `.upm/`), local storage (`storage/`), test artifacts, and any + `.env*` (except `.env.example`) are excluded from git. + +### Edited +- `pnpm-workspace.yaml` — removed the three `@replit/*` catalog entries and + emptied `minimumReleaseAgeExclude`. +- `artifacts/api-server/package.json` — dropped `@google-cloud/storage` and + `google-auth-library`; added `@aws-sdk/client-s3` and + `@aws-sdk/s3-request-presigner`. +- `artifacts/tx-os/package.json` and `artifacts/mockup-sandbox/package.json` + — removed `@replit/*` plugin dependencies. +- `scripts/src/seed.ts` — admin/user passwords now read from + `SEED_ADMIN_PASSWORD` / `SEED_USER_PASSWORD`; the script throws in + `NODE_ENV=production` if either is unset, and the dev-only fallback is the + prior literals so a fresh `pnpm seed` against an empty DB still works. +- `threat_model.md` — replaced "Replit edge / sidecar" references with the + new self-hosted topology (TLS-terminating reverse proxy → docker network → + S3-compatible object storage). + +### Deleted +- `attached_assets/` — 23 MB of legacy uploaded assets unused by the codebase. +- `sedMkjeJm` — stray temp file (a sed copy of `.replit`) at repo root. +- `artifacts/api-server/dist/`, `lib/*/dist/`, all `*.tsbuildinfo` — build + artefacts that should not be tracked. +- `scripts/post-merge.sh` — Replit agent post-merge hook; not relevant + off-Replit. +- `replit.md` — superseded by `README.md`. (Note: `.replit` and `replit.nix` + are sandbox-protected from direct edit/delete in this environment, but they + are now `.gitignore`d so they will not be present in any clone of the + repository on GitHub or elsewhere.) + +## Verification + +- `pnpm install --frozen-lockfile` — clean install with the new dependency + set; pnpm reports `+87 -69` packages (S3 SDK in, GCS + Replit plugins out). +- `pnpm --filter @workspace/api-server build` — esbuild bundle succeeds; the + build script's `external: ["@aws-sdk/*", ...]` list already covered the new + packages. +- API-server test suite — all 12 storage / object-authz tests + (`storage-object-authz.test.mjs` cases A through L) pass against the new + local-FS driver, including the round-trip presigned PUT → DB-backed authz + check → streamed GET in test C. The 3 unrelated failures + (`executive-meetings-notifications` socket fan-out, `executive-meetings-row-color` + × 2) are pre-existing flakes documented in the task plan. + +## Operational notes for first-time deploy + +1. `cp .env.example .env` and fill in every value. +2. `docker compose build` +3. `docker compose up -d db minio minio-init` — Postgres + MinIO + create + buckets. +4. `docker compose run --rm migrate` — apply Drizzle schema + seed admin / + ahmed accounts (one-shot). +5. `docker compose up -d api web` — boot the API and the SPA. +6. Front `web` (port `${WEB_PORT}`) with a TLS-terminating reverse proxy. + +The README's "Production checklist" lists the security-relevant steps required +before fronting the stack with a public domain. + +## What did NOT change + +Per the task scope (and `replit.md` user preferences before its deletion), +the following surfaces were intentionally left untouched: + +- All API and SPA test files (`artifacts/api-server/tests/**`, + `artifacts/tx-os/tests/**`). +- `lib/db/scripts/**` (DB push/pull scripts). +- The PDF renderers (`pdf-renderer.ts`, `pdf-html-renderer.ts`). +- The SPA shell (`App.tsx`, `executive-meetings.tsx`, + `upcoming-meeting-alert.tsx`). +- Locale catalogues (`ar.json`, `en.json`). +- The auto-generated React-Query client (`custom-fetch.ts`). +- The Executive Meetings DB schema (`schema/executive-meetings.ts`). +- Authentication, sessions, rate limiting, and CSRF/CORS middleware — only + the storage subsystem was rewritten. 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/attached_assets/IMG_6277_1777647496054.png b/attached_assets/IMG_6277_1777647496054.png deleted file mode 100644 index 201767ba..00000000 Binary files a/attached_assets/IMG_6277_1777647496054.png and /dev/null differ diff --git a/attached_assets/IMG_6278_1777672596458.png b/attached_assets/IMG_6278_1777672596458.png deleted file mode 100644 index 47d58004..00000000 Binary files a/attached_assets/IMG_6278_1777672596458.png and /dev/null differ diff --git a/attached_assets/Pasted--5--1777634302007_1777634302007.txt b/attached_assets/Pasted--5--1777634302007_1777634302007.txt deleted file mode 100644 index 5ac4c3db..00000000 --- a/attached_assets/Pasted--5--1777634302007_1777634302007.txt +++ /dev/null @@ -1,248 +0,0 @@ -أريد بناء نظام تنبيهات كامل داخل الاجتماعات الاجتماعات: - - -عرض تنبيه قبل الاجتماع القادم بـ 5 دقائق بطريقة واضحة وبسيطة ومناسبة - -======================================== -🧠 منطق النظام (Logic) -======================================== - -1. جلب اجتماعات اليوم فقط. -2. استبعاد الاجتماعات: - - status = cancelled - - status = completed -3. ترتيب الاجتماعات حسب startTime تصاعديًا. -4. تحديد الاجتماع القادم (أقرب اجتماع لم يبدأ بعد). -5. حساب الفرق بين الوقت الحالي ووقت بداية الاجتماع. - -إذا: -remainingTime <= 5 دقائق AND remainingTime > 0 - -→ يظهر التنبيه. - -======================================== -🚫 شروط عدم الظهور -======================================== - -- إذا تم الضغط على "تم" -- إذا تم الضغط على "حذف التنبيه" -- إذا تم إلغاء الاجتماع -- إذا انتهى الاجتماع -- - -======================================== -🖥️ واجهة التنبيه (UI) -======================================== - -يظهر كـ: -Modal أو Toast في أعلى/وسط الشاشة يستطيع المستخدم تحريكه على اي اتجاه. - -وكذالك التنبيه يظهر حتى لو انه خارج تطبيق الاجتماعات يظهر - -يحتوي على: - -- عنوان: -باقي على الاجتماع القادم 5 دقائق - -- اسم الاجتماع - -- الوقت: -مثال: -10:00 -10:30 - - -- تصميم: - - رسمي - - بسيط - - مناسب لجميع الاجهزه - - RTL - -======================================== -🔘 الأزرار داخل التنبيه -======================================== - -1) زر: تم -- إخفاء التنبيه -- لا تعديل على الاجتماع -- تسجيل في Audit Log: - meeting_alert_acknowledged - ----------------------------------------- - -2) زر: تأجيل -عند الضغط: -يفتح Modal يحتوي 3 أقسام: - ----------------------------------------- -🔹 القسم الأول: تأجيل بالدقائق ----------------------------------------- - -خيارات: -- 5 دقائق -- 10 دقائق -- 15 دقيقة -- 30 دقيقة -- 45 دقيقة -- 60 دقيقة - -عند اختيار مدة: -- تعديل startTime و endTime مباشرة -- مثال: - 10:00 - 10:30 → 10:15 - 10:45 -- إغلاق التنبيه -- إعادة ترتيب جدول اليوم حسب الوقت -- إعادة ترقيم عمود "#" -- تسجيل: - meeting_postponed_minutes - ----------------------------------------- -🔹 القسم الثاني: تأجيل إلى وقت لاحق ----------------------------------------- - -نموذج يحتوي: -- التاريخ -- وقت البداية -- وقت النهاية -- ملاحظة (اختياري) - -عند الحفظ: -- تحديث الاجتماع مباشرة -- إذا نفس اليوم: - → إعادة ترتيب الجدول -- إذا يوم آخر: - → إزالته من جدول اليوم -- إعادة ترقيم "م" -- تسجيل: - meeting_rescheduled - ----------------------------------------- -🔹 القسم الثالث: إلغاء الاجتماع ----------------------------------------- - -عند الضغط: -يظهر Confirmation: - -"هل أنت متأكد من إلغاء هذا الاجتماع من جدول اليوم؟" - -الأزرار: -- نعم، إلغاء الاجتماع -- تراجع - -عند التأكيد: -- status = cancelled -- لا حذف من قاعدة البيانات -- إخفاء من جدول اليوم -- إعادة ترتيب الجدول -- إعادة ترقيم "#" -- تسجيل: - meeting_cancelled - ----------------------------------------- - -3) زر: حذف التنبيه -- إخفاء التنبيه فقط -- لا تعديل على الاجتماع -- تسجيل: - meeting_alert_dismissed - -======================================== -⚙️ إعادة ترتيب الجدول (Auto Reordering) -======================================== - -بعد أي عملية: -- تأجيل -- إعادة جدولة -- إلغاء - -يجب: -- sort by startTime ASC -- إعادة ترقيم عمود "#" -- تحديث العرض مباشرة بدون Refresh كامل - -======================================== -⚠️ التعارضات (Conflict) -======================================== - -إذا أصبح هناك تداخل في الأوقات: -- لا تمنع العملية -- اعرض رسالة بسيطة: -"يوجد تعارض محتمل في وقت الاجتماعات" - -======================================== -🗄️ قاعدة البيانات (اختياري إذا غير موجود) -======================================== - -جدول: -executive_meeting_alert_state - -حقول: -- id -- meetingId -- userId -- dismissed (boolean) -- acknowledged (boolean) -- updatedAt - -======================================== -📜 Audit Log -======================================== - -سجل العمليات: - -- meeting_alert_shown -- meeting_alert_acknowledged -- meeting_alert_dismissed -- meeting_postponed_minutes -- meeting_rescheduled -- meeting_cancelled - -======================================== -🧩 Frontend -======================================== - -Component جديد: -UpcomingMeetingAlert - -يرتبط بـ: -- صفحة جدول الاجتماعات اليومية - -يعمل: -- polling أو timer كل 30 ثانية -- حساب الاجتماع القادم -- إظهار التنبيه عند الحاجة - -======================================== -🧪 الاختبار -======================================== - -1. أضف اجتماع بعد 5 دقائق -→ يجب أن يظهر التنبيه - -2. اضغط "تم" -→ يختفي - -3. اضغط "تأجيل 10 دقائق" -→ يتغير الوقت + يعاد ترتيب الجدول - -4. اضغط "تأجيل إلى وقت لاحق" -→ يتحدث الاجتماع - -5. اضغط "إلغاء" -→ يختفي من الجدول - -6. تأكد: - -- كل شيء مباشر - -======================================== -🚫 قيود مهمة -======================================== - -- لا تغيّر النظام العام -- لا تضف Workflow -- لا تضف Roles جديدة -- التعديل فقط داخل Executive Meetings -- الحفاظ على التصميم الرسمي والبسيط - - diff --git a/attached_assets/Pasted--Module--1777290167764_1777290167764.txt b/attached_assets/Pasted--Module--1777290167764_1777290167764.txt deleted file mode 100644 index e9dd719e..00000000 --- a/attached_assets/Pasted--Module--1777290167764_1777290167764.txt +++ /dev/null @@ -1,343 +0,0 @@ -أريد إضافة Module جديد داخل النظام الحالي بدون تخريب أي وظائف موجودة، وبدون إعادة بناء النظام من الصفر. - -اسم التطبيق: -إدارة الاجتماعات التنفيذية -Executive Meetings Management - -مهم جدًا: -- لا تغيّر النظام الحالي أو الصلاحيات الحالية إلا بإضافة ما يلزم لهذا التطبيق. -- لا تحذف أي صفحات أو جداول أو مكونات موجودة. -- نفّذ التطبيق كـ Module مستقل داخل النظام. -- استخدم الصورة المرفقة كمرجع أساسي لتصميم جدول الاجتماعات اليومي. -- المطلوب تصميم iPad First ثم Responsive للجوال والمتصفح. -- الواجهة عربية RTL مع دعم English لاحقًا. - -فكرة التطبيق: -النظام يدير جدول اجتماعات الرئيس التنفيذي يوميًا بشكل حي بدل PDF ثابت وتعديلات واتساب. - -مسار العمل: -الرئيس التنفيذي -↓ -مدير المكتب التنفيذي -↓ -إدارة التنسيق والتوثيق -↓ -تحديث الجدول -↓ -الرئيس يشاهد التحديث من الآيباد - -الأدوار: -1. الرئيس التنفيذي: -- يشاهد جدول اليوم. -- يشاهد الاجتماع القادم. -- يشاهد الوقت المتبقي. -- يشاهد الحضور والمكان والرابط والملاحظات والمرفقات والحالة. -- لا يعدل مباشرة. -- يستطيع فقط إرسال طلب: - - طلب تعديل - - طلب إلغاء - - طلب تأجيل - - إضافة ملاحظة - - طلب إضافة حضور - - طلب حذف حضور - -2. مدير المكتب التنفيذي: -- يستقبل طلبات الرئيس أولًا. -- يراجع الطلب. -- يستطيع: - - اعتماد - - رفض - - تعديل التوجيه - - تحويل الطلب لنوع آخر - - إرسال الطلب لإدارة التنسيق - - تعديل الاجتماع عند الحاجة - -3. إدارة التنسيق والتوثيق: -- تنفذ التوجيهات. -- تعدل وقت الاجتماع. -- تعدل الحضور. -- تعدل القاعة أو رابط الاجتماع. -- ترفع المرفقات. -- توثق التعديلات. -- تحدث الحالة. -- تصدر PDF عند الحاجة. - -4. رئيس التنسيق: -- يعتمد النشر اليومي للجدول. - -5. مشاهد: -- عرض فقط. - -6. مدير النظام: -- إدارة المستخدمين والصلاحيات. - -أضف في القائمة الجانبية Module باسم: -Executive Meetings - -ويحتوي على الصفحات التالية: -- جدول الاجتماعات -- إدارة الاجتماعات -- طلبات التعديل -- موافقات مدير المكتب -- مهام فريق التنسيق -- التنبيهات -- سجل التعديلات -- PDF Export -- Font Settings - -الواجهة الرئيسية: -اسم الصفحة: -جدول الاجتماعات اليومية - -Header: -في أعلى الصفحة من اليسار: -- شعار الجهة الرسمي Logo -- بجانبه: - إدارة الاجتماعات التنفيذية - Executive Meetings Management - -في أعلى الصفحة من اليمين: -- زر تصدير PDF -- اختيار نوع الخط -- حجم الخط: 12 / 14 / 16 / 18 -- سماكة الخط: Regular / Medium / Bold -- تغيير نمط العرض -- اللغة عربي / English -- الوضع الليلي اختياري - -الجدول الرئيسي: -يجب أن يكون بنفس أسلوب الصورة المرفقة رسمي وواضح. - -الأعمدة فقط 4: -| # | اسم الاجتماع | الحضور | الوقت | - -مهم: -- العمود # صغير. -- عمود اسم الاجتماع متوسط. -- عمود الحضور هو الأكبر. -- عمود الوقت واضح. -- رأس الجدول باللون الأزرق الداكن. -- حدود الجدول رسمية وواضحة. -- الخط واضح ومناسب للطباعة والآيباد. -- أضف خيار رفع Logo لاحقًا واستخدامه في رأس الصفحة و PDF. - -تفاصيل الأعمدة: - -1. عمود #: -يعرض رقم الاجتماع اليومي: -1 -2 -3 -4 - -2. عمود اسم الاجتماع: -يعرض اسم الاجتماع الرسمي. -أمثلة: -- اجتماع الإدارة -- اجتماع تحضيري -- اجتماع اعتماد البرامج -- اتصال مرئي عبر Webex - -إذا كان الاجتماع مهم أو ملغي أو يحتاج تنبيه، يمكن تمييزه بلون رسمي بدون مبالغة. - -3. عمود الحضور: -هذا أهم عمود. - -طريقة العرض المطلوبة: -الحضور يكون: -- مرتب -- مرقم -- موزع أفقيًا داخل الخانة -- ليس قائمة عمودية طويلة - -مثال صحيح: -1- أحمد 2- محمد 3- خالد -4- عبدالله 5- فهد - -مثال خاطئ: -1- أحمد -2- محمد -3- خالد -4- عبدالله -5- فهد - -نفّذ هذا تقنيًا باستخدام: -CSS Grid أو Flex Wrap - -بحيث تتوزع الأسماء تلقائيًا داخل الخانة. - -إذا كان الاجتماع يحتوي حضور عن بعد: -Webex / Teams / Zoom - -قسّم الحضور داخل نفس الخانة إلى أقسام: - -الحضور عبر الاتصال المرئي: -1- أحمد 2- محمد 3- خالد - -الحضور الداخلي: -1- عبدالله 2- فهد 3- سالم - -يجب أن يظهر العنوان الفرعي داخل الخانة بلون أزرق رسمي وبخط واضح كما في الصورة المرفقة. - -4. عمود الوقت: -يعرض البداية والنهاية بشكل واضح ويفضل رأسيًا: -10:00 -10:30 - -أو: -10:00 AM -10:30 AM - -حالات الاجتماع: -أضف status لكل اجتماع: -- scheduled = مجدول -- confirmed = مؤكد -- needs_confirmation = يحتاج تأكيد -- modified = معدل -- postponed = مؤجل -- cancelled = ملغي -- completed = مكتمل - -طلبات التعديل: -كل طلب تعديل له دورة حياة: -- new = جديد -- under_office_review = قيد مراجعة مدير المكتب -- approved = معتمد -- sent_to_coordination = مرسل للتنسيق -- in_progress = قيد التنفيذ -- completed = تم التنفيذ -- rejected = مرفوض -- closed = مغلق - -أنواع طلبات الرئيس: -- تعديل وقت -- إلغاء -- تأجيل -- إضافة ملاحظة -- إضافة حضور -- حذف حضور -- تعديل مكان -- تعديل رابط اجتماع - -التنبيهات: -أضف نظام تنبيهات قابل للإعداد من مدير المكتب: -- قبل الاجتماع بـ 15 دقيقة -- قبل الاجتماع بـ 5 دقائق -- عند طلب تعديل من الرئيس -- عند اعتماد مدير المكتب -- عند تنفيذ التعديل -- عند تغيير وقت الاجتماع -- عند إلغاء الاجتماع - -Font Settings: -أضف صفحة إعدادات الخط داخل التطبيق: -- اختيار نوع الخط -- دعم رفع خط مخصص لاحقًا -- حجم الخط 12 / 14 / 16 / 18 -- السماكة Regular / Medium / Bold -- محاذاة النص -- حفظ الإعدادات وتطبيقها على الجدول و PDF - -PDF: -رغم أن الهدف تقليل الاعتماد على PDF، أريد: -- Export PDF -- Download PDF -- Send PDF placeholder فقط إذا لا يوجد نظام إرسال -- Archive PDF by date -- PDF يستخدم نفس تصميم الجدول -- PDF يحتوي Logo Header -- PDF يدعم العربية RTL - -Database: -أضف الجداول أو الموديلات اللازمة بدون كسر الموجود: -- executive_meetings -- executive_meeting_attendees -- executive_meeting_requests -- executive_meeting_tasks -- executive_meeting_notifications -- executive_meeting_audit_logs -- executive_meeting_pdf_archives -- executive_meeting_font_settings - -البيانات المطلوبة للاجتماع: -- id -- dailyNumber -- titleAr -- titleEn -- date -- startTime -- endTime -- location -- meetingUrl -- platform: none/webex/teams/zoom/other -- status -- notes -- attachments -- createdBy -- updatedBy -- timestamps - -الحضور: -- meetingId -- name -- title optional -- attendanceType: internal / virtual / external -- sortOrder - -طلبات التعديل: -- meetingId -- requestedBy -- requestType -- requestDetails -- status -- reviewedBy -- reviewDecision -- reviewNotes -- assignedTo -- timestamps - -المهام: -- requestId -- assignedTo -- taskType -- status -- dueAt -- completedAt -- notes - -Audit Log: -أي تعديل على اجتماع أو طلب يجب تسجيله: -- action -- entityType -- entityId -- oldValue -- newValue -- performedBy -- performedAt - -متطلبات الواجهة: -- تصميم رسمي جدًا. -- مناسب للآيباد أولًا. -- Responsive للجوال والمتصفح. -- دعم RTL ممتاز. -- لا تستخدم تصميم كروت للجدول الرئيسي؛ الجدول الرسمي هو الأساس. -- استخدم الصورة المرفقة كمرجع للشكل النهائي. -- لا تجعل الحضور عموديًا إلا إذا ضاق العرض جدًا في الجوال. - -المطلوب في التنفيذ الأول: -1. إنشاء Module Executive Meetings في القائمة. -2. إنشاء صفحة جدول الاجتماعات اليومية بنفس تصميم الصورة. -3. إضافة بيانات تجريبية مشابهة للصورة. -4. تنفيذ عرض الحضور الأفقي باستخدام flex-wrap أو grid. -5. إضافة Header فيه Logo + اسم التطبيق. -6. إضافة أزرار PDF و Font Settings. -7. إنشاء صفحات Placeholder لباقي الأقسام. -8. تجهيز قاعدة البيانات والـ API الأساسية. -9. عدم كسر أي شيء موجود في النظام الحالي. - -بعد التنفيذ اشرح لي: -- الملفات التي تم تعديلها. -- الملفات التي تم إنشاؤها. -- الجداول التي أضيفت. -- كيف أختبر الصفحة. -- ما الذي لم يتم تنفيذه بعد. \ No newline at end of file diff --git a/attached_assets/Pasted--Note-Note--1777991314708_1777991314709.txt b/attached_assets/Pasted--Note-Note--1777991314708_1777991314709.txt deleted file mode 100644 index 4d85e9ef..00000000 --- a/attached_assets/Pasted--Note-Note--1777991314708_1777991314709.txt +++ /dev/null @@ -1,226 +0,0 @@ -المطلوب تعديل نظام Note ليصبح فيه إرسال ومشاركة بين المستخدمين. - -الفكرة: -المستخدم ينشئ Note جديدة، ثم يختار شخص أو أكثر لإرسالها لهم. -عند الإرسال تظهر النوت عند المتلقي داخل النظام في قسم خاص مثل: -- Received Notes -أو: -- Notes Inbox - -كل Note مستلمة يجب أن تعرض: -- عنوان النوت -- محتوى النوت -- لون النوت -- اسم الشخص المرسل -- تاريخ الإرسال -- حالة القراءة Read / Unread -- إمكانية الرد داخل نفس النوت - --------------------------------------------------- -1) تعديل مفهوم المشاركة --------------------------------------------------- - -بدلاً من مشاركة صامتة فقط، أريد نظام إرسال واضح: - -المرسل: -- ينشئ Note -- يختار اللون -- يكتب المحتوى -- يختار المستلمين -- يضغط Send - -المتلقي: -- تظهر له النوت في Notes Inbox -- يشوف اسم المرسل -- يشوف اللون والمحتوى -- يستطيع الرد داخل نفس النوت -- تبقى نسخة محفوظة عنده حتى يراجعها لاحقاً - --------------------------------------------------- -2) الجداول المطلوبة --------------------------------------------------- - -جدول notes: -- id -- title -- content -- type (note | list) -- color -- ownerUserId -- createdAt -- updatedAt - -جدول note_recipients: -- id -- noteId -- senderUserId -- recipientUserId -- status (unread | read | replied | archived) -- sentAt -- readAt -- archivedAt - -جدول note_replies: -- id -- noteId -- senderUserId -- recipientUserId -- content -- createdAt - -مهم: -- الردود تكون داخل نفس النوت -- كل رد يظهر باسم كاتبه وتاريخه -- المرسل والمتلقي فقط يشوفون المحادثة الخاصة بهم - --------------------------------------------------- -3) واجهة المستخدم --------------------------------------------------- - -داخل صفحة Note أضف تبويبات: - -- My Notes -- Sent Notes -- Received Notes -- Archived - -في My Notes: -- المستخدم يشوف النوتات التي أنشأها - -في Sent Notes: -- المستخدم يشوف النوتات التي أرسلها للآخرين -- يظهر لكل نوت: - - المستلمين - - حالة كل مستلم: unread / read / replied - -في Received Notes: -- المستخدم يشوف النوتات التي وصلته -- يظهر: - - اسم المرسل - - صورة/Avatar المرسل إن وجدت - - عنوان النوت - - اللون - - مختصر المحتوى - - حالة القراءة - --------------------------------------------------- -4) فتح النوت المستلمة --------------------------------------------------- - -عند فتح النوت المستلمة: -- تعرض النوت الأصلية في الأعلى -- يظهر اسم المرسل: - "From: [User Name]" -- يظهر اللون الذي اختاره المرسل -- يظهر تاريخ الإرسال -- أسفلها قسم Replies - -المتلقي يستطيع: -- كتابة رد -- إرسال الرد -- أرشفة النوت -- تمييزها كمقروءة - -بعد الرد: -- تتغير حالة النوت عند المرسل إلى replied -- يظهر الرد عند المرسل داخل Sent Notes أو داخل تفاصيل النوت - --------------------------------------------------- -5) النسخ والحفظ --------------------------------------------------- - -مهم جداً: -- النوت الأصلية تبقى محفوظة عند المرسل -- نسخة مستلمة تبقى محفوظة عند المتلقي في Received Notes -- إذا حذف المرسل النوت من عنده، لا تختفي النسخة من عند المتلقي -- إذا أرشف المتلقي النوت، لا تتأثر نسخة المرسل -- الحذف والأرشفة تكون لكل مستخدم بشكل مستقل - --------------------------------------------------- -6) الصلاحيات --------------------------------------------------- - -- لا يستطيع أي مستخدم فتح Note لم تُرسل له أو لم ينشئها -- المرسل يرى النوت التي أنشأها والردود الخاصة بها -- المتلقي يرى فقط النوتات المرسلة له -- الرد مسموح فقط للمرسل والمتلقي -- Super Admin يمكنه رؤية كل شيء عند الحاجة من لوحة الإدارة - -يجب تطبيق الصلاحيات في Backend وليس Frontend فقط. - --------------------------------------------------- -7) API المطلوبة --------------------------------------------------- - -POST /api/notes -GET /api/notes/my -GET /api/notes/sent -GET /api/notes/received -GET /api/notes/:id - -POST /api/notes/:id/send -Body: -{ - "recipientUserIds": ["userId1", "userId2"] -} - -POST /api/notes/:id/reply -Body: -{ - "content": "reply text" -} - -POST /api/notes/:id/read -POST /api/notes/:id/archive - --------------------------------------------------- -8) الإشعارات داخل النظام --------------------------------------------------- - -عند وصول Note جديدة: -- يظهر Badge على قائمة Note -- يظهر Toast notification -- في Received Notes تظهر كـ Unread - -عند وصول رد: -- يظهر تنبيه للمرسل -- يتم تحديث حالة النوت إلى replied - --------------------------------------------------- -9) تجربة المستخدم --------------------------------------------------- - -- زر واضح باسم Send Note -- اختيار المستلمين من قائمة المستخدمين -- بحث عن المستخدم بالاسم -- عرض Chips للمستلمين المختارين -- Confirm قبل الإرسال -- Toast بعد الإرسال: - "Note sent successfully" - --------------------------------------------------- -10) دعم العربية والإنجليزية --------------------------------------------------- - -كل النصوص يجب أن تكون ضمن i18n: -- العربية RTL -- الإنجليزية LTR - --------------------------------------------------- -11) المطلوب النهائي --------------------------------------------------- - -نفّذ الميزة بشكل حقيقي: -- Database schema -- Backend routes -- Frontend UI -- Authorization -- Notifications badges -- Responsive design -- Clean code -- Production-ready - -مهم: -لا تجعلها مجرد مشاركة عادية. -أريدها كأنها "مراسلة Note داخل النظام": -يرسل المستخدم Note، تظهر عند المتلقي، يستطيع الرد عليها، وتبقى نسخة محفوظة عند الطرفين. \ No newline at end of file diff --git a/attached_assets/Pasted--Note-TeaBoy-Note--1776753298386_1776753298386.txt b/attached_assets/Pasted--Note-TeaBoy-Note--1776753298386_1776753298386.txt deleted file mode 100644 index 62e8a181..00000000 --- a/attached_assets/Pasted--Note-TeaBoy-Note--1776753298386_1776753298386.txt +++ /dev/null @@ -1,229 +0,0 @@ -الموضوع: إنشاء نظام "Note" داخل منصة TeaBoy - -أريد إضافة ميزة داخل النظام باسم "Note"، تكون نظام ملاحظات بسيط واحترافي يشبه Google Keep، مع دعم: - -- الملاحظات النصية (Note) -- القوائم (List / Checklist) -- الألوان -- التثبيت (Pin) -- المشاركة مع الأعضاء -- الصلاحيات - --------------------------------------------------- -1) الكيان الرئيسي --------------------------------------------------- - -إنشاء جدول: -notes - -الحقول: -- id -- title -- content (للنص) -- type (note | list) -- color (hex) -- isPinned (boolean) -- isArchived (boolean) -- isDeleted (soft delete) -- ownerUserId -- createdAt -- updatedAt - --------------------------------------------------- -2) عناصر القائمة (Checklist) --------------------------------------------------- - -جدول: -note_items - -الحقول: -- id -- noteId -- text -- isCompleted (boolean) -- sortOrder -- createdAt -- updatedAt - --------------------------------------------------- -3) المشاركة (Sharing) --------------------------------------------------- - -جدول: -note_shares - -الحقول: -- id -- noteId -- userId -- permission (view | edit) -- createdAt - -المنطق: -- صاحب الملاحظة هو المالك -- يمكنه مشاركة الملاحظة مع مستخدمين -- view = مشاهدة فقط -- edit = تعديل كامل - --------------------------------------------------- -4) الواجهة (UI) --------------------------------------------------- - -إنشاء صفحة: -Note - -التصميم: -- Grid مثل Google Keep -- بطاقات (Cards) لكل ملاحظة -- خلفية فاتحة وألوان هادئة -- Responsive (جوال / آيباد / كمبيوتر) - -كل بطاقة تعرض: -- العنوان -- محتوى مختصر أو checklist -- اللون -- نوعها (Note / List) -- أيقونة إذا كانت مشتركة -- حالة التثبيت - --------------------------------------------------- -5) الوظائف الأساسية --------------------------------------------------- - -- إنشاء Note -- إنشاء List -- تعديل -- حذف (Soft delete) -- أرشفة -- تثبيت / إلغاء التثبيت -- تغيير اللون -- مشاركة مع مستخدمين -- إلغاء المشاركة - --------------------------------------------------- -6) نافذة الإنشاء --------------------------------------------------- - -Modal أو صفحة تحتوي على: - -- title -- type (note / list) -- content (إذا Note) -- checklist (إذا List) -- color picker -- pin toggle -- share users + permission - --------------------------------------------------- -7) الفلاتر والتنظيم --------------------------------------------------- - -إضافة Tabs: - -- All -- My Notes -- Shared With Me -- Pinned -- Archived - -إضافة: -- بحث -- فلترة حسب اللون -- ترتيب (الأحدث / الأقدم / المثبتة) - --------------------------------------------------- -8) الصلاحيات --------------------------------------------------- - -Super Admin: -- يرى كل الملاحظات - -Admin: -- يرى ملاحظاته + المشاركات - -User: -- يرى: - - ملاحظاته - - الملاحظات المشتركة معه - -مهم: -- التحقق من الصلاحيات في backend - --------------------------------------------------- -9) API --------------------------------------------------- - -GET /api/notes -GET /api/notes/:id -POST /api/notes -PATCH /api/notes/:id -DELETE /api/notes/:id - -POST /api/notes/:id/pin -POST /api/notes/:id/archive -POST /api/notes/:id/share -DELETE /api/notes/:id/share/:userId - -Checklist: -POST /api/notes/:id/items -PATCH /api/notes/:id/items/:itemId -DELETE /api/notes/:id/items/:itemId - --------------------------------------------------- -10) الألوان --------------------------------------------------- - -ألوان افتراضية: -- أصفر -- أخضر -- أزرق -- أحمر -- بنفسجي -- رمادي - -+ Color Picker - -يجب أن تكون: -- هادئة -- مناسبة للـ Light UI -- النص واضح فوقها - --------------------------------------------------- -11) تحسينات UX --------------------------------------------------- - -- Auto-save -- Toast notifications -- Skeleton loading -- Confirm delete -- Empty states -- عرض صورة المستخدمين المشاركين -- Badge: - - Note / List - - Shared / Private - --------------------------------------------------- -12) الترجمة --------------------------------------------------- - -دعم: -- العربية (RTL) -- الإنجليزية (LTR) - -باستخدام i18n - --------------------------------------------------- -13) التنفيذ --------------------------------------------------- - -أريد: -- Database schema -- Backend كامل -- Frontend pages -- Clean code -- Responsive -- Production-ready - --------------------------------------------------- -ملاحظة: -الميزة يجب أن تكون حقيقية (Functional) وليست مجرد واجهة شكلية. \ No newline at end of file diff --git a/attached_assets/Pasted--PDF--1777887197581_1777887197582.txt b/attached_assets/Pasted--PDF--1777887197581_1777887197582.txt deleted file mode 100644 index 965e864b..00000000 --- a/attached_assets/Pasted--PDF--1777887197581_1777887197582.txt +++ /dev/null @@ -1,113 +0,0 @@ -المشكلة ما زالت موجودة في PDF: -الجدول لا يحافظ على الصف الواحد. بعض الخلايا مثل الوقت أو الحضور تظهر في سطر مستقل، وهذا خطأ. - -المطلوب إصلاحه: -- كل اجتماع يجب أن يكون داخل صف واحد فقط . -- داخل نفس الصف يجب أن تكون الخلايا بالترتيب: - الرقم | الاجتماع | الحضور | الوقت -- ممنوع أن يظهر الوقت في صف مستقل. -- ممنوع أن يظهر الحضور في صف منفصل عن الاجتماع. -- ممنوع إنشاء جديد لمجرد أن النص طويل. -- النص الطويل يجب أن يلتف داخل نفس الخلية فقط، وليس ينقل الخلية لسطر جديد. - -السبب المتوقع: -أنت تبني الجدول بطريقة خاطئة أو تستخدم div/flex داخل جسم الجدول، أو تتعامل مع الأسطر الطويلة كصفوف جديدة. - -الحل المطلوب: -ابنِ PDF باستخدام table حقيقي: - - - - - - - - - - - - - - - - - - -
#الاجتماعالحضورالوقت
7اجتماع بخصوص اعداد PDF - 1- علي - 2- طارق سعد محمد - 3- عبدالله سلطان العلي - 4- باسل محمد خالد - 11:20-11:30
- -مهم جداً: -لا تستخدم display:flex على tr أو td. -لا تستخدم CSS grid للجدول. -لا تستخدم div كبديل للجدول. -لا تقسم الاجتماع إلى أكثر من tr. -النص الطويل يلتف داخل td فقط. - -CSS إلزامي: - -table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - direction: rtl; -} - -tr { - break-inside: avoid; - page-break-inside: avoid; -} - -th, td { - padding: 0 4px !important; - margin: 0 !important; - line-height: 1.1 !important; - vertical-align: middle !important; - text-align: center; - white-space: normal; - overflow-wrap: break-word; - word-break: normal; -} - -td:nth-child(1), th:nth-child(1) { - width: 8%; -} - -td:nth-child(2), th:nth-child(2) { - width: 30%; -} - -td:nth-child(3), th:nth-child(3) { - width: 42%; -} - -td:nth-child(4), th:nth-child(4) { - width: 20%; - direction: ltr; - unicode-bidi: isolate; - white-space: nowrap; -} - -.attendees-inline { - display: inline; - direction: rtl; - text-align: center; -} - -.attendee-item { - display: inline-block; - white-space: nowrap; - margin: 0 6px; - unicode-bidi: isolate; -} - -الأهم: -أي اجتماع في البيانات يجب أن ينتج عنه واحد فقط. -لا تجعل التفاف النص ينشئ صفوف جديدة. -إذا كان هناك دمج rowSpan أو colSpan في النظام، طبقه على نفس ولا تنشئ صفوف إضافية. - -النتيجة المطلوبة: -الوقت، الاجتماع، الحضور، والرقم يظهرون دائماً في نفس صف الاجتماع، والنص يلتف داخل الخلية فقط. \ No newline at end of file diff --git a/attached_assets/Pasted--PDF--1777887253073_1777887253074.txt b/attached_assets/Pasted--PDF--1777887253073_1777887253074.txt deleted file mode 100644 index 965e864b..00000000 --- a/attached_assets/Pasted--PDF--1777887253073_1777887253074.txt +++ /dev/null @@ -1,113 +0,0 @@ -المشكلة ما زالت موجودة في PDF: -الجدول لا يحافظ على الصف الواحد. بعض الخلايا مثل الوقت أو الحضور تظهر في سطر مستقل، وهذا خطأ. - -المطلوب إصلاحه: -- كل اجتماع يجب أن يكون داخل صف واحد فقط . -- داخل نفس الصف يجب أن تكون الخلايا بالترتيب: - الرقم | الاجتماع | الحضور | الوقت -- ممنوع أن يظهر الوقت في صف مستقل. -- ممنوع أن يظهر الحضور في صف منفصل عن الاجتماع. -- ممنوع إنشاء جديد لمجرد أن النص طويل. -- النص الطويل يجب أن يلتف داخل نفس الخلية فقط، وليس ينقل الخلية لسطر جديد. - -السبب المتوقع: -أنت تبني الجدول بطريقة خاطئة أو تستخدم div/flex داخل جسم الجدول، أو تتعامل مع الأسطر الطويلة كصفوف جديدة. - -الحل المطلوب: -ابنِ PDF باستخدام table حقيقي: - - - - - - - - - - - - - - - - - - -
#الاجتماعالحضورالوقت
7اجتماع بخصوص اعداد PDF - 1- علي - 2- طارق سعد محمد - 3- عبدالله سلطان العلي - 4- باسل محمد خالد - 11:20-11:30
- -مهم جداً: -لا تستخدم display:flex على tr أو td. -لا تستخدم CSS grid للجدول. -لا تستخدم div كبديل للجدول. -لا تقسم الاجتماع إلى أكثر من tr. -النص الطويل يلتف داخل td فقط. - -CSS إلزامي: - -table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - direction: rtl; -} - -tr { - break-inside: avoid; - page-break-inside: avoid; -} - -th, td { - padding: 0 4px !important; - margin: 0 !important; - line-height: 1.1 !important; - vertical-align: middle !important; - text-align: center; - white-space: normal; - overflow-wrap: break-word; - word-break: normal; -} - -td:nth-child(1), th:nth-child(1) { - width: 8%; -} - -td:nth-child(2), th:nth-child(2) { - width: 30%; -} - -td:nth-child(3), th:nth-child(3) { - width: 42%; -} - -td:nth-child(4), th:nth-child(4) { - width: 20%; - direction: ltr; - unicode-bidi: isolate; - white-space: nowrap; -} - -.attendees-inline { - display: inline; - direction: rtl; - text-align: center; -} - -.attendee-item { - display: inline-block; - white-space: nowrap; - margin: 0 6px; - unicode-bidi: isolate; -} - -الأهم: -أي اجتماع في البيانات يجب أن ينتج عنه واحد فقط. -لا تجعل التفاف النص ينشئ صفوف جديدة. -إذا كان هناك دمج rowSpan أو colSpan في النظام، طبقه على نفس ولا تنشئ صفوف إضافية. - -النتيجة المطلوبة: -الوقت، الاجتماع، الحضور، والرقم يظهرون دائماً في نفس صف الاجتماع، والنص يلتف داخل الخلية فقط. \ No newline at end of file diff --git a/attached_assets/Pasted--PDF--1777888517954_1777888517955.txt b/attached_assets/Pasted--PDF--1777888517954_1777888517955.txt deleted file mode 100644 index 6893964c..00000000 --- a/attached_assets/Pasted--PDF--1777888517954_1777888517955.txt +++ /dev/null @@ -1,104 +0,0 @@ -الإخراج الحالي غير مقبول. المطلوب إصلاح جدول PDF بحيث يكون كل اجتماع داخل صف واحد فقط. - -المشكلة الحالية: -- الوقت يظهر أحياناً في سطر مستقل فوق أو تحت الاجتماع. -- بعض الحضور يخرجون من صف الاجتماع. -- الصف لا يحافظ على نفس المحاذاة بين رقم الاجتماع، اسم الاجتماع، الحضور، والوقت. -- المطلوب أن يكون كل اجتماع عبارة عن واحد فقط يحتوي 4 خلايا: - رقم الاجتماع | اسم الاجتماع | الحضور | الوقت - -مهم جداً: -لا تجعل الوقت أو الحضور عناصر منفصلة خارج صف الجدول. -كل بيانات الاجتماع يجب أن تكون داخل نفس tr. - -الشكل المطلوب داخل HTML: - - - 6 - اجتماع خاص - -
- -1 محمد خالد - -2 سعيد عبدالله - -3 طارق علي سعيد -
- - 10:30-10:40 - - -ممنوع: -- ممنوع طباعة الوقت قبل tr أو بعد tr. -- ممنوع جعل الحضور div خارج td. -- ممنوع استخدام position:absolute داخل الجدول. -- ممنوع استخدام display:flex على tr أو table. -- ممنوع تقسيم الاجتماع الواحد إلى أكثر من tr. - -CSS المطلوب: - -@media print { - table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - } - - tr { - page-break-inside: avoid; - break-inside: avoid; - } - - th, td { - vertical-align: top; - padding: 6px 8px; - text-align: right; - white-space: normal; - overflow-wrap: break-word; - } - - .num-cell { - width: 8%; - text-align: center; - } - - .meeting-cell { - width: 30%; - direction: rtl; - text-align: right; - } - - .attendees-cell { - width: 42%; - direction: rtl; - text-align: right; - } - - .time-cell { - width: 20%; - direction: ltr; - unicode-bidi: isolate; - text-align: center; - white-space: nowrap; - } - - .attendees-inline { - display: flex; - flex-wrap: wrap; - gap: 2px 14px; - direction: rtl; - text-align: right; - line-height: 1.6; - } - - .attendee-item { - display: inline-block; - white-space: nowrap; - direction: rtl; - unicode-bidi: isolate; - } -} - -المطلوب النهائي: -كل صف اجتماع يظهر مثل النموذج المرجعي: -رقم الاجتماع واسم الاجتماع والحضور والوقت على نفس السطر الأساسي. -الحضور يكون inline داخل خانة الحضور، 3 إلى 4 أسماء تقريباً في السطر. -الوقت يبقى دائماً داخل عمود الوقت في نفس صف الاجتماع. \ No newline at end of file diff --git a/attached_assets/Pasted--PDF-1-padding--1777893010554_1777893010554.txt b/attached_assets/Pasted--PDF-1-padding--1777893010554_1777893010554.txt deleted file mode 100644 index 65838dd8..00000000 --- a/attached_assets/Pasted--PDF-1-padding--1777893010554_1777893010554.txt +++ /dev/null @@ -1,125 +0,0 @@ -ممتاز، لكن نحتاج إصلاحات نهائية في PDF: - -1. المسافات ضيقة جداً -- زِد padding داخل الخلايا. -- زِد line-height للنصوص. -- اجعل الجدول مريح للقراءة. -- لا تجعل النص ملاصق للخلايا. - -CSS: -th, td { - padding: 10px 12px; - line-height: 1.8; - vertical-align: top; -} - -.attendees-inline { - gap: 6px 18px; - line-height: 1.8; -} - -2. العنوان واللوقو -- العنوان "قائمة بأسماء حضور الاجتماعات" يجب أن يكون أعلى الصفحة وليس أسفلها. -- أضف اللوقو أعلى الصفحة إذا كان موجوداً في إعدادات النظام. -- إذا لا يوجد لوقو في الإعدادات، لا تترك مساحة فارغة. - -المطلوب: -PDF header في الأعلى يحتوي: -- اللوقو يمين أو يسار حسب تصميم النظام -- العنوان في المنتصف أو يمين حسب النموذج -- ثم الجدول تحته - -3. عكس ألوان النظام في PDF -حالياً ألوان الخط والصفوف لا تنعكس. -المطلوب أن PDF يستخدم نفس إعدادات النظام القابلة للتعديل: -- لون الخط العام -- لون عنوان الجدول -- لون خلفية الهيدر -- لون حدود الجدول -- لون الصف المحدد أو الصف المميز -- لون الخلفية إذا كان المستخدم محددها - -مهم: -لا تستخدم ألوان ثابتة hardcoded داخل CSS. -استخدم CSS variables أو إعدادات theme من النظام. - -مثال: -:root { - --pdf-font-color: var(--system-text-color); - --pdf-header-bg: var(--system-header-bg); - --pdf-header-color: var(--system-header-text); - --pdf-border-color: var(--system-border-color); - --pdf-selected-row-bg: var(--system-selected-row-bg); -} - -.pdf-table { - color: var(--pdf-font-color); -} - -.pdf-table th { - background: var(--pdf-header-bg); - color: var(--pdf-header-color); - border-color: var(--pdf-border-color); -} - -.pdf-table td { - border-color: var(--pdf-border-color); -} - -.pdf-table tr.selected-row td { - background: var(--pdf-selected-row-bg); -} - -4. موضوع دمج الخلايا -أنا سأستخدم دمج في النظام. -المطلوب أن PDF يعكس الدمج مثل النظام تماماً. - -إذا كان في النظام: -- دمج صفوف -- دمج أعمدة -- دمج خلايا للحضور أو الوقت - -يجب أن ينعكس في PDF باستخدام: -- colspan -- rowspan - -مثال: -نص مدمج -وقت مشترك - -ممنوع: -- تجاهل الدمج عند الطباعة -- تحويل الدمج إلى نص عادي -- كسر الجدول بسبب الدمج - -5. ألوان الصفوف المحددة -إذا المستخدم اختار لون صف معين داخل النظام، يجب أن يظهر نفس اللون في PDF. -مثال: -- صف اجتماع محدد بلون أصفر -- صف مهم بلون رمادي -- صف مميز بلون أزرق فاتح - -يجب حفظ اللون مع بيانات الصف، ثم استخدامه في PDF: - - -6. الخط -استمر باستخدام: -DIN Next LT Arabic - -ويجب أن يكون مطبق على: -- العنوان -- الجدول -- الحضور -- الوقت -- الفوتر - -7. المطلوب النهائي -قبل توليد PDF: -- اقرأ إعدادات الثيم من النظام -- اقرأ اللوقو من إعدادات النظام -- اقرأ ألوان الصفوف المحددة -- اقرأ إعدادات الدمج -- طبّقها كلها داخل HTML الخاص بالطباعة - -لا أريد PDF بتصميم ثابت. -أريد PDF يعكس إعدادات النظام فعلياً. \ No newline at end of file diff --git a/attached_assets/Pasted--PDF-PDF--1777882873928_1777882873930.txt b/attached_assets/Pasted--PDF-PDF--1777882873928_1777882873930.txt deleted file mode 100644 index 04b6fe29..00000000 --- a/attached_assets/Pasted--PDF-PDF--1777882873928_1777882873930.txt +++ /dev/null @@ -1,151 +0,0 @@ -أريد تعديل طباعة PDF لجدول حضور الاجتماعات بحيث يكون مطابقاً قدر الإمكان لملف PDF المرفق. - -المهم جداً: -لا تغيّر فكرة التصميم. أريد نفس شكل الملف المرجعي: -- عنوان في الأعلى: قائمة بأسماء حضور الاجتماعات -- جدول بسيط بالأعمدة: - # | الاجتماع | الحضور | الوقت -- الاتجاه عربي RTL -- الخط المستخدم في كامل PDF: - DIN Next LT Arabic - -تنسيق الحضور المطلوب: -- أسماء الحضور تكون داخل نفس خلية الحضور بشكل أفقي inline. -- لا تجعل كل اسم في سطر مستقل. -- كل سطر يحتوي تقريباً 3 إلى 4 أسماء حسب طول الاسم. -- إذا زاد عدد الحضور، ينزل تلقائياً للسطر التالي. -- الشكل المطلوب مثل: - -1 طارق عبدالله -2 فهد احمد -3 عبدالرحمن عبدالله - -4 د. سالم الشمري -5 سليمان محمد - -مهم: -- أريد نفس أسلوب الترقيم الموجود في النموذج: - -1 الاسم - -2 الاسم - -3 الاسم -- لا تستخدم قائمة عمودية طويلة. -- لا تستخدم ol/li إذا كانت تسبب انقلاب الأرقام. -- لا تجعل الترقيم يظهر بشكل خاطئ أو منفصل عن الاسم. -- الرقم والاسم يكونان قطعة واحدة مع بعض داخل span. - -تنسيق الجدول: -- استخدم table حقيقي للطباعة. -- لا تستخدم flex لتقسيم أعمدة الجدول. -- العرض كامل الصفحة. -- حجم الصفحة A4 portrait. -- الهوامش مناسبة. -- النصوص محاذاة يمين. -- الوقت يكون واضح داخل عمود الوقت ولا يطلع خارج الجدول. -- الوقت يكون بصيغة مثل: - 12:20-12:50 - 1:00-1:30 - -أحجام الأعمدة المقترحة: -- الرقم #: 8% -- الاجتماع: 30% -- الحضور: 42% -- الوقت: 20% - -CSS مقترح: - -@media print { - @page { - size: A4 portrait; - margin: 12mm; - } - - body { - font-family: "DIN Next LT Arabic", Arial, sans-serif; - direction: rtl; - text-align: right; - color: #000; - } - - table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - } - - th, td { - padding: 6px 8px; - vertical-align: top; - text-align: right; - white-space: normal; - word-break: normal; - overflow-wrap: break-word; - } - - th:nth-child(1), td:nth-child(1) { - width: 8%; - text-align: center; - } - - th:nth-child(2), td:nth-child(2) { - width: 30%; - } - - th:nth-child(3), td:nth-child(3) { - width: 42%; - } - - th:nth-child(4), td:nth-child(4) { - width: 20%; - text-align: center; - direction: ltr; - unicode-bidi: isolate; - white-space: nowrap; - } - - .attendees-inline { - display: flex; - flex-wrap: wrap; - direction: rtl; - text-align: right; - gap: 2px 14px; - line-height: 1.7; - } - - .attendee-item { - display: inline-block; - white-space: nowrap; - direction: rtl; - unicode-bidi: isolate; - } - - .meeting-title { - direction: rtl; - text-align: right; - line-height: 1.6; - } - - .footer { - margin-top: 16px; - font-size: 12px; - } -} - -طريقة توليد الحضور داخل HTML: -بدلاً من: -
    -
  • طارق عبدالله
  • -
- -استخدم: -
- -1 طارق عبدالله - -2 فهد احمد - -3 عبدالرحمن عبدالله - -4 د. سالم الشمري - -5 سليمان محمد -
- -المطلوب النهائي: -PDF يكون مثل الملف المرجعي تماماً من حيث: -- بساطة الجدول -- الحضور في نفس السطر -- 3 إلى 4 أسماء تقريباً في السطر -- عدم تداخل الأعمدة -- عدم خروج الوقت من مكانه -- الالتزام بخط DIN Next LT Arabic -- عدم تحويل الحضور إلى قائمة عمودية \ No newline at end of file diff --git a/attached_assets/Pasted--PDF-PDF--1777885005658_1777885005659.txt b/attached_assets/Pasted--PDF-PDF--1777885005658_1777885005659.txt deleted file mode 100644 index 7b7afa1b..00000000 --- a/attached_assets/Pasted--PDF-PDF--1777885005658_1777885005659.txt +++ /dev/null @@ -1,151 +0,0 @@ -أريد إصلاح تصدير PDF لجدول الاجتماعات بشكل نهائي. - -⚠️ مهم: -التصميم الخارجي للـPDF ثابت (العنوان + الفوتر + حجم الصفحة + الخط DIN Next LT Arabic). -التعديل فقط داخل الجدول. - -================================== -أولاً: إصلاح المشاكل الحالية -================================== - -1. لا يوجد أي فراغات بين الصفوف: -- لا padding عمودي -- لا margin -- ارتفاع الصف = حجم المحتوى فقط - -2. كل اجتماع يجب أن يكون صف واحد: -- الرقم + اسم الاجتماع + الحضور + الوقت في نفس الصف -- لا يظهر الوقت في صف منفصل أبداً - -3. إصلاح الترقيم: -- لا يبدأ من 4 أو رقم خاطئ -- استخدم رقم الاجتماع الحقيقي من البيانات - -4. إصلاح ترتيب الحضور: -- لا يظهر بشكل مقلوب مثل: - -5 سالم -4 علي -- الترتيب يكون: - 1- محمد 2- علي 3- طارق - -5. الدمج: -- أي merge في النظام (rowSpan / colSpan) يجب أن يظهر نفسه في PDF -- لا تعيد بناء الجدول بدون الدمج - -================================== -ثانياً: تنسيق الحضور -================================== - -- الحضور في نفس السطر (inline) -- كل سطر يحتوي 3 إلى 4 أسماء تقريباً -- إذا زاد العدد → ينزل سطر جديد تلقائياً -- لا تجعل كل اسم في سطر مستقل - -مثال: -1- محمد 2- علي 3- طارق -4- سالم 5- عبدالله - -================================== -ثالثاً: محاذاة المحتوى -================================== - -- عمود "الاجتماع" في المنتصف (center) -- عمود "الحضور" في المنتصف -- عمود "الوقت" في المنتصف -- vertical-align: middle - -================================== -رابعاً: عكس تنسيق المستخدم (مهم جداً) -================================== - -داخل الجدول فقط: - -- Bold → يظهر Bold في PDF -- Center / Right / Left → يظهر نفس المحاذاة -- لون الخط → يظهر نفسه -- لون الخلفية → يظهر نفسه -- حجم الخط → يظهر نفسه - -⚠️ لا تجعل CSS العام يلغي تنسيقات الخلايا - -الحل: -- استخدم inline style لكل خلية -- مثال: - - - المحتوى - - -================================== -خامساً: CSS المطلوب -================================== - -@media print { - table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - border-spacing: 0; - } - - th, td { - padding: 0 4px !important; - margin: 0 !important; - line-height: 1.05 !important; - vertical-align: middle !important; - text-align: center; - } - - .attendees-inline { - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: center; - gap: 0 10px; - direction: rtl; - } - - .attendee-item { - white-space: nowrap; - unicode-bidi: isolate; - } - - .time-cell { - direction: ltr; - white-space: nowrap; - text-align: center; - } -} - -================================== -سادساً: شرط مهم جداً -================================== - -لا تبنِ PDF من DOM مكسور. - -- استخدم نفس بيانات الجدول في النظام -- طبّق rowSpan و colSpan كما هي -- كل صف في PDF = صف حقيقي في النظام - -================================== -النتيجة المطلوبة: -================================== - -PDF مطابق للنظام من حيث: -- بدون فراغات -- الحضور inline -- الترقيم صحيح -- الدمج صحيح -- الوقت داخل الصف -- المحاذاة في المنتصف -- جميع التنسيقات (Bold / Color / Alignment) تنعكس \ No newline at end of file diff --git a/attached_assets/Pasted-Build-a-full-stack-internal-web-platform-called-TeaBoy-_1776675317094.txt b/attached_assets/Pasted-Build-a-full-stack-internal-web-platform-called-TeaBoy-_1776675317094.txt deleted file mode 100644 index be908486..00000000 --- a/attached_assets/Pasted-Build-a-full-stack-internal-web-platform-called-TeaBoy-_1776675317094.txt +++ /dev/null @@ -1,131 +0,0 @@ -Build a full-stack internal web platform called "TeaBoy OS" as a responsive operating-system-style web interface inspired by iOS/Android, designed to run internally on a Mac Server using Docker. - -Core stack: -- Frontend: Next.js (App Router) + React + TypeScript + Tailwind CSS -- Backend: Node.js + Express + Socket.IO -- Database: PostgreSQL -- Deployment: Docker Compose - -General requirements: -- The UI must be fully responsive and work well on desktop, iPad, and mobile browsers. -- The platform must support both Arabic and English from day one. -- Arabic must be the default language. -- Full RTL support for Arabic and LTR support for English. -- All text must come from an i18n system, not hardcoded strings. -- Include a language switcher in the UI. -- Save user language preference. - -Main UI / OS Interface: -1. Home screen / desktop interface: - - Glassmorphism visual style - - Beautiful OS-like home screen - - Responsive grid of app icons - - Top status bar with: - - live clock - - username - - notifications bell - - Bottom dock for frequently used apps - - Smooth hover and click interactions - - Arabic-friendly modern styling - -2. Dynamic apps system: - - Apps must be stored in the database in a table called `apps` - - Admin can create, edit, activate, deactivate, and sort apps - - Apps automatically appear on the home screen without hardcoding - - Each app should support bilingual content: - - name_ar - - name_en - - description_ar - - description_en - - Also include: - - slug - - icon_name - - route - - color - - is_active - - is_system - - sort_order - -3. Service app ("خدماتي"): - - Show internal services such as tea, Arabic coffee, water, etc. - - Display them as elegant responsive cards - - Admin must be able to create, edit, and delete services dynamically - - Services must support bilingual content: - - name_ar - - name_en - - description_ar - - description_en - - Also include: - - image_url - - price - - is_available - - category if useful - -4. Communication app: - - Internal chat app - - Support direct one-to-one conversations - - Support group conversations - - Use Socket.IO / WebSockets for realtime messaging - - Realtime notifications for new messages - - Store conversations, participants, messages, and read states in the database - -5. Authentication and RBAC: - - Users table - - Roles table - - Permissions table - - User roles mapping - - Role permissions mapping - - App visibility should be permission-aware - - Admin role should manage apps, services, and users - -Database requirements: -Use PostgreSQL and create a scalable schema including at minimum: -- users -- roles -- user_roles -- permissions -- role_permissions -- apps -- services -- service_categories (optional but recommended) -- conversations -- conversation_participants -- messages -- message_reads -- notifications -- app_permissions (recommended) - -Important schema requirements: -- users must include preferred_language with default 'ar' -- apps must include bilingual fields: - - name_ar, name_en, description_ar, description_en -- services must include bilingual fields: - - name_ar, name_en, description_ar, description_en - -Technical requirements: -- Create a clean modular folder structure -- Use reusable components -- Make the architecture scalable for future apps -- Add seed data for demo apps and demo services -- Prepare the project to run with Docker Compose -- Keep the code clean and production-minded - -Deliverables required: -1. docker-compose.yml -2. frontend and backend Dockerfiles -3. PostgreSQL schema / init.sql -4. Next.js home screen UI -5. basic backend API structure -6. Socket.IO server setup -7. sample admin dashboard structure for managing apps and services -8. i18n structure for Arabic and English -9. seed data - -Important: -After implementation, provide a clear report of: -- what was created -- which files were added -- which features are fully working now -- which features are scaffolded only -- what is still pending -Do not give a vague summary. Be precise and explicit. \ No newline at end of file diff --git a/attached_assets/Pasted-Build-a-production-style-bilingual-web-app-called-TeaBo_1776684329770.txt b/attached_assets/Pasted-Build-a-production-style-bilingual-web-app-called-TeaBo_1776684329770.txt deleted file mode 100644 index d1c44dcb..00000000 --- a/attached_assets/Pasted-Build-a-production-style-bilingual-web-app-called-TeaBo_1776684329770.txt +++ /dev/null @@ -1,251 +0,0 @@ -Build a production-style bilingual web app called "TeaBoy" that looks and behaves like a lightweight internal OS interface, optimized for desktop, tablet, and mobile browsers. The interface must support both Arabic and English, with RTL/LTR support, and use a modern light creative UI inspired by iOS-style glassmorphism. Do not use the name "TeaBoy OS" in the UI. Use only "TeaBoy" or "النظام" where appropriate. - -The system must include these major parts: - -1) Authentication -- No public registration page at all. -- Only admin can create users. -- Users log in with username + password. -- First time a user logs in, they must be forced to change their password before accessing the system. -- Store a boolean like mustChangePassword=true for new users. -- After password change, redirect them to their home screen. -- Secure password hashing required. -- Include session management and protected routes. - -2) Main TeaBoy User Experience -- After login, the user lands on a Home Screen similar to a mobile/desktop operating system launcher. -- Display app icons in a responsive grid. -- Show only apps the logged-in user is allowed to access. -- Each app card/icon should include: - - app icon/image - - app name - - optional short description -- Clicking an app opens: - - external URL in a new tab if the app is external - - internal route if the app is internal -- Top status bar should include: - - current time - - logged in user name - - language switcher (Arabic / English) - - profile / logout menu -- Design should be modern, elegant, light, and smooth with glassmorphism feel. - -3) Admin Panel -Create a professional Admin sidebar menu with the following structure: - -- Dashboard -- User Management - - View Users - - Create User - - Permissions -- Application Management - - View Applications - - Add Application - - Application Permissions -- Settings - -Admin panel must be clean, enterprise-style, bilingual, and fully responsive. - -4) Dashboard -Create an admin dashboard page showing summary cards: -- Total Users -- Total Applications -- Active Users count (basic/mock if needed) -- Admin Users count -- Recently Added Users -- Recently Added Applications - -5) User Management -Create a full professional user management module. - -A) View Users page -- Table/cards of all users -- Search by name or username -- Filter by role -- Show: - - name - - username - - role - - status - - number of assigned apps - - created date -- Clicking a user opens a detailed page or drawer showing: - - full name - - username - - role - - assigned apps - - mustChangePassword status - - created at - - last updated -- Add edit and delete actions. - -B) Create User page -Fields: -- Full name -- Username -- Temporary password -- Role (admin / user) -- Assigned applications (multi-select or checkboxes) -- Status (active / inactive) -- mustChangePassword should default to true for newly created users - -Behavior: -- Admin creates the user manually -- No email verification flow required -- No public signup - -C) Permissions page -- Admin can manage which apps are assigned to which users -- Support direct app assignment per user -- Simple and professional permission model: - - admin has full access - - user only sees assigned apps -- Avoid overly complex RBAC for now; use app-based permissions. - -6) Application Management -Create a full professional application management module. - -A) View Applications page -- Show all applications in a table/cards view -- Search by app name -- Show: - - app icon - - app name - - type (internal / external) - - url or route - - visibility summary - - created date - -B) Add Application page -Fields: -- Application name -- External URL or internal route -- Upload/select application image/icon -- Description -- Type: - - external - - internal -- Visibility / assignment options: - - admin only - - all users - - selected users only -- Status active/inactive - -Behavior: -- External applications open in a new tab -- Internal applications route inside the platform -- Image upload should be supported, or at minimum image URL with upload-ready component design -- Store application metadata cleanly - -C) Application Permissions page -- Admin can select an application and choose which users can access it -- Also allow assigning applications from the user side -- Keep permission logic consistent - -7) Data Models -Use clean relational or structured schema for at least: -- users -- applications -- user_applications (many-to-many) -- sessions if needed - -Suggested user fields: -- id -- fullName -- username -- passwordHash -- role -- status -- mustChangePassword -- createdAt -- updatedAt - -Suggested application fields: -- id -- name -- description -- icon -- type -- url -- internalRoute -- visibilityMode -- status -- createdAt -- updatedAt - -Suggested pivot: -- userId -- applicationId - -8) Authorization Rules -- Admin can access admin panel, manage users, manage apps, and view dashboard -- User cannot access admin pages -- User can only see assigned applications on home screen -- If app visibility is admin only, regular users never see it -- If user is inactive, block login -- If mustChangePassword is true, force password change before anything else - -9) UI / UX Requirements -- Bilingual Arabic and English from day one -- Full RTL support for Arabic -- Light modern color palette, soft creative colors, not harsh or dark-heavy -- Glassmorphism-inspired cards and panels -- Responsive for desktop, tablet, mobile -- Smooth transitions -- Professional sidebar admin layout -- User home screen should feel like a polished internal OS / app launcher -- Avoid clutter -- Typography must support Arabic beautifully -- Keep code structured and scalable - -10) Technical Requirements -- Use React + TypeScript + Tailwind CSS -- Create clear folder structure -- Component-based architecture -- Use mock/local database if needed, but structure it as if production-ready -- Include route guards -- Include seed data: - - one admin account - - few sample users - - few sample applications -- Include clean reusable components: - - sidebar - - top bar - - app card - - user table - - application table - - forms - - permission selectors - - password change screen -- Use best practices for state management and forms -- Add validation for create user and create application forms - -11) Important Business Rules -- There must be no self-registration page -- Admin is the only one who can create users -- New users must change password on first login -- Users only see allowed applications -- Admin can add external apps by entering: - - app name - - external link - - icon/image - - description - - who can access it -- Build this as a real internal admin-controlled system, not a public SaaS signup product - -12) Deliverables -Generate: -- full app structure -- pages -- components -- routes -- mock data / seed -- authentication flow -- admin dashboard -- user management -- application management -- forced password change flow -- bilingual UI scaffolding -- responsive polished frontend - -Please implement the system end-to-end in a clean, maintainable way with a professional architecture. Start with a strong frontend and working mocked backend/data layer if needed, but structure the code so it can later connect to a real database and real authentication backend easily. \ No newline at end of file diff --git a/attached_assets/Pasted-Implement-the-Service-Orders-workflow-with-receiver-per_1776795235281.txt b/attached_assets/Pasted-Implement-the-Service-Orders-workflow-with-receiver-per_1776795235281.txt deleted file mode 100644 index 2cbdd528..00000000 --- a/attached_assets/Pasted-Implement-the-Service-Orders-workflow-with-receiver-per_1776795235281.txt +++ /dev/null @@ -1,105 +0,0 @@ -Implement the Service Orders workflow with receiver permissions. - -Business logic: -1. Admin must be able to create users and control who is allowed to receive service orders. -2. Add a dedicated permission for receiving orders, such as: - - orders.receive -3. Any user with this permission should see incoming new service orders in the orders system. -4. When a client places an order: - - the order should appear for all users who have the order-receiving permission - - they should get a realtime notification / bell alert -5. The first receiver who clicks "Confirm Receipt" becomes the assigned handler of the order. -6. Once confirmed: - - set assigned_to to that receiver - - update order status from pending to received - - notify the client that the order has been received and is being prepared -7. The client should see statuses like: - - pending - - received - - preparing - - completed - - cancelled - -Client-side requirements: -- Before placing the order, the client can add notes/comments -- Remove service description from the order form / order display if currently shown there -- The service description should remain only in the catalog managed by admin -- The client should see clear status updates: - - order received - - preparing - -Admin requirements: -- Admin manages the catalog only -- Admin manages users and permissions -- Admin can decide which users are allowed to receive service orders - -Database / backend requirements: -1. Add permission: - - orders.receive -2. Add service_orders table if not already added, with at least: - - id - - user_id - - service_id - - notes - - status - - assigned_to - - created_at - - updated_at -3. Status flow: - - pending - - received - - preparing - - completed - - cancelled - -API requirements: -- POST /api/orders -- GET /api/orders/my -- GET /api/orders/incoming -- PATCH /api/orders/:id/confirm-receipt -- PATCH /api/orders/:id/status - -Rules: -- normal client can create order and view own orders -- only users with orders.receive permission can see incoming orders -- only one receiver can confirm receipt successfully -- once receipt is confirmed, notify the client -- use realtime bell/notification if Socket.IO already exists - -Frontend requirements: -1. Client: -- services catalog -- order modal/form -- notes field -- submit order -- my orders page with status - -2. Receiver side: -- incoming orders page/list -- bell notification for new orders -- button: Confirm Receipt -- after receipt, ability to move status to: - - preparing - - completed - - cancelled - -3. Admin: -- manage users -- assign or remove orders.receive permission -- manage services catalog only - -Important: -- Keep Arabic and English support -- Keep RTL support -- Keep it simple and operational -- Remove unnecessary description duplication from order screens - -After implementation, provide a precise report: -1. files changed -2. database changes -3. permission changes -4. API endpoints added -5. how incoming orders are routed to permitted users -6. how confirm receipt works -7. what notifications are implemented -8. what is still pending \ No newline at end of file diff --git a/attached_assets/Pasted-Update-the-admin-system-to-support-User-Management-with_1776841000012.txt b/attached_assets/Pasted-Update-the-admin-system-to-support-User-Management-with_1776841000012.txt deleted file mode 100644 index b7877aa6..00000000 --- a/attached_assets/Pasted-Update-the-admin-system-to-support-User-Management-with_1776841000012.txt +++ /dev/null @@ -1,95 +0,0 @@ -Update the admin system to support User Management with submenus: -- Users -- Groups - -Requirements: - -1. Admin menu structure: -Inside User Management, create two submenus: -- Users -- Groups - -2. Users page: -Create a proper users management page that includes: -- list all users -- search/filter by: - - name - - username - - email - - group - - active/disabled status -- create new user -- edit user -- activate/deactivate user -- delete user -- assign one or more groups to the user - -Display in users table: -- full name -- username -- email -- preferred language -- active status -- groups -- actions - -3. Groups page: -Create full group management: -- create group -- edit group -- delete group -- search groups -- show group name -- description -- number of users -- allowed apps - -Each group must support: -- name -- description -- assigned users -- assigned apps - -4. Access model: -Users should get access to apps based on their group assignments. -Do not rely only on individual user roles. -Support one user belonging to one or more groups. - -5. Database: -Add tables if not already present: -- groups -- user_groups -- group_apps - -Suggested structure: -- groups: id, name, description, created_at, updated_at -- user_groups: user_id, group_id -- group_apps: group_id, app_id - -6. Business use case: -We want to create a group called "TeaBoy" that receives service orders only. -There may be multiple groups in the future. -Example: -- TeaBoy group can access incoming orders app -- Admin group can access full admin system -- other groups can access different apps - -7. UI: -- Arabic-first -- RTL friendly -- clean admin layout -- use clear labels for groups and applications - -8. Important: -- show group membership clearly in user details -- show app access clearly in group details -- make this scalable for future groups and apps - -After implementation, provide a precise report: -1. files changed -2. database tables added/updated -3. pages added -4. API endpoints added -5. how group-to-app permission works -6. how users are assigned to groups -7. what still remains incomplete \ No newline at end of file diff --git a/attached_assets/executive-meetings-2026-05-03_(11)_1777833579006.pdf b/attached_assets/executive-meetings-2026-05-03_(11)_1777833579006.pdf deleted file mode 100644 index b3c59e3e..00000000 Binary files a/attached_assets/executive-meetings-2026-05-03_(11)_1777833579006.pdf and /dev/null differ diff --git a/attached_assets/fonts_1777290331863.zip b/attached_assets/fonts_1777290331863.zip deleted file mode 100644 index df47c599..00000000 Binary files a/attached_assets/fonts_1777290331863.zip and /dev/null differ diff --git a/attached_assets/fonts_1777661666793.zip b/attached_assets/fonts_1777661666793.zip deleted file mode 100644 index df47c599..00000000 Binary files a/attached_assets/fonts_1777661666793.zip and /dev/null differ diff --git a/attached_assets/fonts_1777875174094.zip b/attached_assets/fonts_1777875174094.zip deleted file mode 100644 index df47c599..00000000 Binary files a/attached_assets/fonts_1777875174094.zip and /dev/null differ diff --git a/attached_assets/image_1776680956310.png b/attached_assets/image_1776680956310.png deleted file mode 100644 index 503bfd67..00000000 Binary files a/attached_assets/image_1776680956310.png and /dev/null differ diff --git a/attached_assets/image_1776683484376.png b/attached_assets/image_1776683484376.png deleted file mode 100644 index 0c10279b..00000000 Binary files a/attached_assets/image_1776683484376.png and /dev/null differ diff --git a/attached_assets/image_1776687865627.png b/attached_assets/image_1776687865627.png deleted file mode 100644 index f4116d42..00000000 Binary files a/attached_assets/image_1776687865627.png and /dev/null differ diff --git a/attached_assets/image_1776688198958.png b/attached_assets/image_1776688198958.png deleted file mode 100644 index 5703d69c..00000000 Binary files a/attached_assets/image_1776688198958.png and /dev/null differ diff --git a/attached_assets/image_1776698656101.png b/attached_assets/image_1776698656101.png deleted file mode 100644 index f9374a55..00000000 Binary files a/attached_assets/image_1776698656101.png and /dev/null differ diff --git a/attached_assets/image_1776754196650.png b/attached_assets/image_1776754196650.png deleted file mode 100644 index 685bdafd..00000000 Binary files a/attached_assets/image_1776754196650.png and /dev/null differ diff --git a/attached_assets/image_1776754222587.png b/attached_assets/image_1776754222587.png deleted file mode 100644 index 950dc491..00000000 Binary files a/attached_assets/image_1776754222587.png and /dev/null differ diff --git a/attached_assets/image_1776765361664.png b/attached_assets/image_1776765361664.png deleted file mode 100644 index 4ae3fd43..00000000 Binary files a/attached_assets/image_1776765361664.png and /dev/null differ diff --git a/attached_assets/image_1776775314113.png b/attached_assets/image_1776775314113.png deleted file mode 100644 index 7f6abcc6..00000000 Binary files a/attached_assets/image_1776775314113.png and /dev/null differ diff --git a/attached_assets/image_1776778302348.png b/attached_assets/image_1776778302348.png deleted file mode 100644 index f33bf1bd..00000000 Binary files a/attached_assets/image_1776778302348.png and /dev/null differ diff --git a/attached_assets/image_1776792400979.png b/attached_assets/image_1776792400979.png deleted file mode 100644 index 20cebf8a..00000000 Binary files a/attached_assets/image_1776792400979.png and /dev/null differ diff --git a/attached_assets/image_1776792826852.png b/attached_assets/image_1776792826852.png deleted file mode 100644 index 143a11e9..00000000 Binary files a/attached_assets/image_1776792826852.png and /dev/null differ diff --git a/attached_assets/image_1776793052221.png b/attached_assets/image_1776793052221.png deleted file mode 100644 index e9fd3a5b..00000000 Binary files a/attached_assets/image_1776793052221.png and /dev/null differ diff --git a/attached_assets/image_1776793737998.png b/attached_assets/image_1776793737998.png deleted file mode 100644 index ee6e5f5b..00000000 Binary files a/attached_assets/image_1776793737998.png and /dev/null differ diff --git a/attached_assets/image_1776794402443.png b/attached_assets/image_1776794402443.png deleted file mode 100644 index c028d936..00000000 Binary files a/attached_assets/image_1776794402443.png and /dev/null differ diff --git a/attached_assets/image_1776794418708.png b/attached_assets/image_1776794418708.png deleted file mode 100644 index c028d936..00000000 Binary files a/attached_assets/image_1776794418708.png and /dev/null differ diff --git a/attached_assets/image_1776794487527.png b/attached_assets/image_1776794487527.png deleted file mode 100644 index c028d936..00000000 Binary files a/attached_assets/image_1776794487527.png and /dev/null differ diff --git a/attached_assets/image_1776840026164.png b/attached_assets/image_1776840026164.png deleted file mode 100644 index afbdea70..00000000 Binary files a/attached_assets/image_1776840026164.png and /dev/null differ diff --git a/attached_assets/image_1776855398946.png b/attached_assets/image_1776855398946.png deleted file mode 100644 index a73fddd8..00000000 Binary files a/attached_assets/image_1776855398946.png and /dev/null differ diff --git a/attached_assets/image_1777280177090.png b/attached_assets/image_1777280177090.png deleted file mode 100644 index ea3d41f9..00000000 Binary files a/attached_assets/image_1777280177090.png and /dev/null differ diff --git a/attached_assets/image_1777286933640.png b/attached_assets/image_1777286933640.png deleted file mode 100644 index 0a0be157..00000000 Binary files a/attached_assets/image_1777286933640.png and /dev/null differ diff --git a/attached_assets/image_1777288639337.png b/attached_assets/image_1777288639337.png deleted file mode 100644 index faa2ccaf..00000000 Binary files a/attached_assets/image_1777288639337.png and /dev/null differ diff --git a/attached_assets/image_1777362650457.png b/attached_assets/image_1777362650457.png deleted file mode 100644 index 5ef9ce90..00000000 Binary files a/attached_assets/image_1777362650457.png and /dev/null differ diff --git a/attached_assets/image_1777363139653.png b/attached_assets/image_1777363139653.png deleted file mode 100644 index 1e350d41..00000000 Binary files a/attached_assets/image_1777363139653.png and /dev/null differ diff --git a/attached_assets/image_1777409651042.png b/attached_assets/image_1777409651042.png deleted file mode 100644 index 65677ba8..00000000 Binary files a/attached_assets/image_1777409651042.png and /dev/null differ diff --git a/attached_assets/image_1777409970525.png b/attached_assets/image_1777409970525.png deleted file mode 100644 index df02a386..00000000 Binary files a/attached_assets/image_1777409970525.png and /dev/null differ diff --git a/attached_assets/image_1777410806198.png b/attached_assets/image_1777410806198.png deleted file mode 100644 index df02a386..00000000 Binary files a/attached_assets/image_1777410806198.png and /dev/null differ diff --git a/attached_assets/image_1777411868760.png b/attached_assets/image_1777411868760.png deleted file mode 100644 index 6563fba4..00000000 Binary files a/attached_assets/image_1777411868760.png and /dev/null differ diff --git a/attached_assets/image_1777411920031.png b/attached_assets/image_1777411920031.png deleted file mode 100644 index 808bffd4..00000000 Binary files a/attached_assets/image_1777411920031.png and /dev/null differ diff --git a/attached_assets/image_1777445941920.png b/attached_assets/image_1777445941920.png deleted file mode 100644 index e9bb61bb..00000000 Binary files a/attached_assets/image_1777445941920.png and /dev/null differ diff --git a/attached_assets/image_1777459009163.png b/attached_assets/image_1777459009163.png deleted file mode 100644 index 640a8526..00000000 Binary files a/attached_assets/image_1777459009163.png and /dev/null differ diff --git a/attached_assets/image_1777459120920.png b/attached_assets/image_1777459120920.png deleted file mode 100644 index 5de4908e..00000000 Binary files a/attached_assets/image_1777459120920.png and /dev/null differ diff --git a/attached_assets/image_1777459200133.png b/attached_assets/image_1777459200133.png deleted file mode 100644 index 7deb6c74..00000000 Binary files a/attached_assets/image_1777459200133.png and /dev/null differ diff --git a/attached_assets/image_1777465070880.png b/attached_assets/image_1777465070880.png deleted file mode 100644 index 485ebeef..00000000 Binary files a/attached_assets/image_1777465070880.png and /dev/null differ diff --git a/attached_assets/image_1777465114581.png b/attached_assets/image_1777465114581.png deleted file mode 100644 index dde7b6e8..00000000 Binary files a/attached_assets/image_1777465114581.png and /dev/null differ diff --git a/attached_assets/image_1777468765151.png b/attached_assets/image_1777468765151.png deleted file mode 100644 index 5881879e..00000000 Binary files a/attached_assets/image_1777468765151.png and /dev/null differ diff --git a/attached_assets/image_1777474162567.png b/attached_assets/image_1777474162567.png deleted file mode 100644 index 01f8d842..00000000 Binary files a/attached_assets/image_1777474162567.png and /dev/null differ diff --git a/attached_assets/image_1777483517660.png b/attached_assets/image_1777483517660.png deleted file mode 100644 index d8dd233a..00000000 Binary files a/attached_assets/image_1777483517660.png and /dev/null differ diff --git a/attached_assets/image_1777483753088.png b/attached_assets/image_1777483753088.png deleted file mode 100644 index af7fa794..00000000 Binary files a/attached_assets/image_1777483753088.png and /dev/null differ diff --git a/attached_assets/image_1777486651752.png b/attached_assets/image_1777486651752.png deleted file mode 100644 index 95db07a4..00000000 Binary files a/attached_assets/image_1777486651752.png and /dev/null differ diff --git a/attached_assets/image_1777487556544.png b/attached_assets/image_1777487556544.png deleted file mode 100644 index 7a6a679d..00000000 Binary files a/attached_assets/image_1777487556544.png and /dev/null differ diff --git a/attached_assets/image_1777487951379.png b/attached_assets/image_1777487951379.png deleted file mode 100644 index ea47d5b3..00000000 Binary files a/attached_assets/image_1777487951379.png and /dev/null differ diff --git a/attached_assets/image_1777488889425.png b/attached_assets/image_1777488889425.png deleted file mode 100644 index d5b97288..00000000 Binary files a/attached_assets/image_1777488889425.png and /dev/null differ diff --git a/attached_assets/image_1777489697109.png b/attached_assets/image_1777489697109.png deleted file mode 100644 index a0770c10..00000000 Binary files a/attached_assets/image_1777489697109.png and /dev/null differ diff --git a/attached_assets/image_1777489969250.png b/attached_assets/image_1777489969250.png deleted file mode 100644 index ac7d3e66..00000000 Binary files a/attached_assets/image_1777489969250.png and /dev/null differ diff --git a/attached_assets/image_1777490800700.png b/attached_assets/image_1777490800700.png deleted file mode 100644 index a181cfe4..00000000 Binary files a/attached_assets/image_1777490800700.png and /dev/null differ diff --git a/attached_assets/image_1777527422272.png b/attached_assets/image_1777527422272.png deleted file mode 100644 index 897c31a3..00000000 Binary files a/attached_assets/image_1777527422272.png and /dev/null differ diff --git a/attached_assets/image_1777530629783.png b/attached_assets/image_1777530629783.png deleted file mode 100644 index e66d7a7b..00000000 Binary files a/attached_assets/image_1777530629783.png and /dev/null differ diff --git a/attached_assets/image_1777532761124.png b/attached_assets/image_1777532761124.png deleted file mode 100644 index 272802a3..00000000 Binary files a/attached_assets/image_1777532761124.png and /dev/null differ diff --git a/attached_assets/image_1777539229471.png b/attached_assets/image_1777539229471.png deleted file mode 100644 index 67f4d758..00000000 Binary files a/attached_assets/image_1777539229471.png and /dev/null differ diff --git a/attached_assets/image_1777542752130.png b/attached_assets/image_1777542752130.png deleted file mode 100644 index cead79ea..00000000 Binary files a/attached_assets/image_1777542752130.png and /dev/null differ diff --git a/attached_assets/image_1777546032538.png b/attached_assets/image_1777546032538.png deleted file mode 100644 index 8da77a51..00000000 Binary files a/attached_assets/image_1777546032538.png and /dev/null differ diff --git a/attached_assets/image_1777550894540.png b/attached_assets/image_1777550894540.png deleted file mode 100644 index 5fcd5750..00000000 Binary files a/attached_assets/image_1777550894540.png and /dev/null differ diff --git a/attached_assets/image_1777552559341.png b/attached_assets/image_1777552559341.png deleted file mode 100644 index 4186962c..00000000 Binary files a/attached_assets/image_1777552559341.png and /dev/null differ diff --git a/attached_assets/image_1777553124397.png b/attached_assets/image_1777553124397.png deleted file mode 100644 index 58037097..00000000 Binary files a/attached_assets/image_1777553124397.png and /dev/null differ diff --git a/attached_assets/image_1777572263009.png b/attached_assets/image_1777572263009.png deleted file mode 100644 index a03f7a91..00000000 Binary files a/attached_assets/image_1777572263009.png and /dev/null differ diff --git a/attached_assets/image_1777572336273.png b/attached_assets/image_1777572336273.png deleted file mode 100644 index fcdb89a5..00000000 Binary files a/attached_assets/image_1777572336273.png and /dev/null differ diff --git a/attached_assets/image_1777573430870.png b/attached_assets/image_1777573430870.png deleted file mode 100644 index 607e8f47..00000000 Binary files a/attached_assets/image_1777573430870.png and /dev/null differ diff --git a/attached_assets/image_1777621048800.png b/attached_assets/image_1777621048800.png deleted file mode 100644 index db80ba0e..00000000 Binary files a/attached_assets/image_1777621048800.png and /dev/null differ diff --git a/attached_assets/image_1777622402281.png b/attached_assets/image_1777622402281.png deleted file mode 100644 index db80ba0e..00000000 Binary files a/attached_assets/image_1777622402281.png and /dev/null differ diff --git a/attached_assets/image_1777622413346.png b/attached_assets/image_1777622413346.png deleted file mode 100644 index e9e66621..00000000 Binary files a/attached_assets/image_1777622413346.png and /dev/null differ diff --git a/attached_assets/image_1777622612721.png b/attached_assets/image_1777622612721.png deleted file mode 100644 index 109b76e7..00000000 Binary files a/attached_assets/image_1777622612721.png and /dev/null differ diff --git a/attached_assets/image_1777625835465.png b/attached_assets/image_1777625835465.png deleted file mode 100644 index cd434180..00000000 Binary files a/attached_assets/image_1777625835465.png and /dev/null differ diff --git a/attached_assets/image_1777640776387.png b/attached_assets/image_1777640776387.png deleted file mode 100644 index 26ecbbea..00000000 Binary files a/attached_assets/image_1777640776387.png and /dev/null differ diff --git a/attached_assets/image_1777644196126.png b/attached_assets/image_1777644196126.png deleted file mode 100644 index 10e6584f..00000000 Binary files a/attached_assets/image_1777644196126.png and /dev/null differ diff --git a/attached_assets/image_1777647615495.png b/attached_assets/image_1777647615495.png deleted file mode 100644 index 234215a4..00000000 Binary files a/attached_assets/image_1777647615495.png and /dev/null differ diff --git a/attached_assets/image_1777647693653.png b/attached_assets/image_1777647693653.png deleted file mode 100644 index 234215a4..00000000 Binary files a/attached_assets/image_1777647693653.png and /dev/null differ diff --git a/attached_assets/image_1777648597560.png b/attached_assets/image_1777648597560.png deleted file mode 100644 index 591d1ece..00000000 Binary files a/attached_assets/image_1777648597560.png and /dev/null differ diff --git a/attached_assets/image_1777648639642.png b/attached_assets/image_1777648639642.png deleted file mode 100644 index e87f154e..00000000 Binary files a/attached_assets/image_1777648639642.png and /dev/null differ diff --git a/attached_assets/image_1777658141607.png b/attached_assets/image_1777658141607.png deleted file mode 100644 index 30a5d37b..00000000 Binary files a/attached_assets/image_1777658141607.png and /dev/null differ diff --git a/attached_assets/image_1777659678269.png b/attached_assets/image_1777659678269.png deleted file mode 100644 index 1f3423a3..00000000 Binary files a/attached_assets/image_1777659678269.png and /dev/null differ diff --git a/attached_assets/image_1777660282348.png b/attached_assets/image_1777660282348.png deleted file mode 100644 index e165e290..00000000 Binary files a/attached_assets/image_1777660282348.png and /dev/null differ diff --git a/attached_assets/image_1777661035768.png b/attached_assets/image_1777661035768.png deleted file mode 100644 index 736bb957..00000000 Binary files a/attached_assets/image_1777661035768.png and /dev/null differ diff --git a/attached_assets/image_1777661594804.png b/attached_assets/image_1777661594804.png deleted file mode 100644 index 6c51f7c6..00000000 Binary files a/attached_assets/image_1777661594804.png and /dev/null differ diff --git a/attached_assets/image_1777664764914.png b/attached_assets/image_1777664764914.png deleted file mode 100644 index a2d9ebf8..00000000 Binary files a/attached_assets/image_1777664764914.png and /dev/null differ diff --git a/attached_assets/image_1777664776320.png b/attached_assets/image_1777664776320.png deleted file mode 100644 index a2d9ebf8..00000000 Binary files a/attached_assets/image_1777664776320.png and /dev/null differ diff --git a/attached_assets/image_1777664828219.png b/attached_assets/image_1777664828219.png deleted file mode 100644 index a733954f..00000000 Binary files a/attached_assets/image_1777664828219.png and /dev/null differ diff --git a/attached_assets/image_1777670459003.png b/attached_assets/image_1777670459003.png deleted file mode 100644 index d0466ddc..00000000 Binary files a/attached_assets/image_1777670459003.png and /dev/null differ diff --git a/attached_assets/image_1777670622165.png b/attached_assets/image_1777670622165.png deleted file mode 100644 index 7ec9f44a..00000000 Binary files a/attached_assets/image_1777670622165.png and /dev/null differ diff --git a/attached_assets/image_1777709990981.png b/attached_assets/image_1777709990981.png deleted file mode 100644 index 1c280682..00000000 Binary files a/attached_assets/image_1777709990981.png and /dev/null differ diff --git a/attached_assets/image_1777712121479.png b/attached_assets/image_1777712121479.png deleted file mode 100644 index 06487245..00000000 Binary files a/attached_assets/image_1777712121479.png and /dev/null differ diff --git a/attached_assets/image_1777714337008.png b/attached_assets/image_1777714337008.png deleted file mode 100644 index 089f163a..00000000 Binary files a/attached_assets/image_1777714337008.png and /dev/null differ diff --git a/attached_assets/image_1777717363629.png b/attached_assets/image_1777717363629.png deleted file mode 100644 index c207b591..00000000 Binary files a/attached_assets/image_1777717363629.png and /dev/null differ diff --git a/attached_assets/image_1777788351106.png b/attached_assets/image_1777788351106.png deleted file mode 100644 index e4cb163e..00000000 Binary files a/attached_assets/image_1777788351106.png and /dev/null differ diff --git a/attached_assets/image_1777795205779.png b/attached_assets/image_1777795205779.png deleted file mode 100644 index 8f21ce07..00000000 Binary files a/attached_assets/image_1777795205779.png and /dev/null differ diff --git a/attached_assets/image_1777795571536.png b/attached_assets/image_1777795571536.png deleted file mode 100644 index 464a7d89..00000000 Binary files a/attached_assets/image_1777795571536.png and /dev/null differ diff --git a/attached_assets/image_1777800288856.png b/attached_assets/image_1777800288856.png deleted file mode 100644 index 7c288e1a..00000000 Binary files a/attached_assets/image_1777800288856.png and /dev/null differ diff --git a/attached_assets/image_1777801237097.png b/attached_assets/image_1777801237097.png deleted file mode 100644 index 0673c158..00000000 Binary files a/attached_assets/image_1777801237097.png and /dev/null differ diff --git a/attached_assets/image_1777801984136.png b/attached_assets/image_1777801984136.png deleted file mode 100644 index 5ee1751a..00000000 Binary files a/attached_assets/image_1777801984136.png and /dev/null differ diff --git a/attached_assets/image_1777802688880.png b/attached_assets/image_1777802688880.png deleted file mode 100644 index 0c2dc751..00000000 Binary files a/attached_assets/image_1777802688880.png and /dev/null differ diff --git a/attached_assets/image_1777802888029.png b/attached_assets/image_1777802888029.png deleted file mode 100644 index b4e3647c..00000000 Binary files a/attached_assets/image_1777802888029.png and /dev/null differ diff --git a/attached_assets/image_1777805215408.png b/attached_assets/image_1777805215408.png deleted file mode 100644 index 77ca4ab0..00000000 Binary files a/attached_assets/image_1777805215408.png and /dev/null differ diff --git a/attached_assets/image_1777805279729.png b/attached_assets/image_1777805279729.png deleted file mode 100644 index 100d8d26..00000000 Binary files a/attached_assets/image_1777805279729.png and /dev/null differ diff --git a/attached_assets/image_1777807125881.png b/attached_assets/image_1777807125881.png deleted file mode 100644 index 240a366a..00000000 Binary files a/attached_assets/image_1777807125881.png and /dev/null differ diff --git a/attached_assets/image_1777807942923.png b/attached_assets/image_1777807942923.png deleted file mode 100644 index 13da0da5..00000000 Binary files a/attached_assets/image_1777807942923.png and /dev/null differ diff --git a/attached_assets/image_1777808736116.png b/attached_assets/image_1777808736116.png deleted file mode 100644 index 8a10b982..00000000 Binary files a/attached_assets/image_1777808736116.png and /dev/null differ diff --git a/attached_assets/image_1777808812049.png b/attached_assets/image_1777808812049.png deleted file mode 100644 index 0684b96a..00000000 Binary files a/attached_assets/image_1777808812049.png and /dev/null differ diff --git a/attached_assets/image_1777809195342.png b/attached_assets/image_1777809195342.png deleted file mode 100644 index a53079f1..00000000 Binary files a/attached_assets/image_1777809195342.png and /dev/null differ diff --git a/attached_assets/image_1777809561647.png b/attached_assets/image_1777809561647.png deleted file mode 100644 index 0fc0883e..00000000 Binary files a/attached_assets/image_1777809561647.png and /dev/null differ diff --git a/attached_assets/image_1777809722331.png b/attached_assets/image_1777809722331.png deleted file mode 100644 index f6ddc886..00000000 Binary files a/attached_assets/image_1777809722331.png and /dev/null differ diff --git a/attached_assets/image_1777809950347.png b/attached_assets/image_1777809950347.png deleted file mode 100644 index 6e1ac75f..00000000 Binary files a/attached_assets/image_1777809950347.png and /dev/null differ diff --git a/attached_assets/image_1777811643566.png b/attached_assets/image_1777811643566.png deleted file mode 100644 index 07819be7..00000000 Binary files a/attached_assets/image_1777811643566.png and /dev/null differ diff --git a/attached_assets/image_1777811806428.png b/attached_assets/image_1777811806428.png deleted file mode 100644 index 1653e853..00000000 Binary files a/attached_assets/image_1777811806428.png and /dev/null differ diff --git a/attached_assets/image_1777812288491.png b/attached_assets/image_1777812288491.png deleted file mode 100644 index 4a052ae5..00000000 Binary files a/attached_assets/image_1777812288491.png and /dev/null differ diff --git a/attached_assets/image_1777812365394.png b/attached_assets/image_1777812365394.png deleted file mode 100644 index eb5258fc..00000000 Binary files a/attached_assets/image_1777812365394.png and /dev/null differ diff --git a/attached_assets/image_1777812522884.png b/attached_assets/image_1777812522884.png deleted file mode 100644 index cf626641..00000000 Binary files a/attached_assets/image_1777812522884.png and /dev/null differ diff --git a/attached_assets/image_1777812594661.png b/attached_assets/image_1777812594661.png deleted file mode 100644 index c0dbe5b7..00000000 Binary files a/attached_assets/image_1777812594661.png and /dev/null differ diff --git a/attached_assets/image_1777813135409.png b/attached_assets/image_1777813135409.png deleted file mode 100644 index 6f273c2c..00000000 Binary files a/attached_assets/image_1777813135409.png and /dev/null differ diff --git a/attached_assets/image_1777813753986.png b/attached_assets/image_1777813753986.png deleted file mode 100644 index 968c1735..00000000 Binary files a/attached_assets/image_1777813753986.png and /dev/null differ diff --git a/attached_assets/image_1777818423550.png b/attached_assets/image_1777818423550.png deleted file mode 100644 index 63990567..00000000 Binary files a/attached_assets/image_1777818423550.png and /dev/null differ diff --git a/attached_assets/image_1777820658970.png b/attached_assets/image_1777820658970.png deleted file mode 100644 index 429a070e..00000000 Binary files a/attached_assets/image_1777820658970.png and /dev/null differ diff --git a/attached_assets/image_1777821236320.png b/attached_assets/image_1777821236320.png deleted file mode 100644 index 8553e164..00000000 Binary files a/attached_assets/image_1777821236320.png and /dev/null differ diff --git a/attached_assets/image_1777821337879.png b/attached_assets/image_1777821337879.png deleted file mode 100644 index ec5fb4ef..00000000 Binary files a/attached_assets/image_1777821337879.png and /dev/null differ diff --git a/attached_assets/image_1777823484450.png b/attached_assets/image_1777823484450.png deleted file mode 100644 index e4ab6e72..00000000 Binary files a/attached_assets/image_1777823484450.png and /dev/null differ diff --git a/attached_assets/image_1777823494099.png b/attached_assets/image_1777823494099.png deleted file mode 100644 index e4ab6e72..00000000 Binary files a/attached_assets/image_1777823494099.png and /dev/null differ diff --git a/attached_assets/image_1777823767122.png b/attached_assets/image_1777823767122.png deleted file mode 100644 index 0107150c..00000000 Binary files a/attached_assets/image_1777823767122.png and /dev/null differ diff --git a/attached_assets/image_1777824312844.png b/attached_assets/image_1777824312844.png deleted file mode 100644 index 5a833edf..00000000 Binary files a/attached_assets/image_1777824312844.png and /dev/null differ diff --git a/attached_assets/image_1777824372704.png b/attached_assets/image_1777824372704.png deleted file mode 100644 index 7ab32277..00000000 Binary files a/attached_assets/image_1777824372704.png and /dev/null differ diff --git a/attached_assets/image_1777830387518.png b/attached_assets/image_1777830387518.png deleted file mode 100644 index 13a764f4..00000000 Binary files a/attached_assets/image_1777830387518.png and /dev/null differ diff --git a/attached_assets/image_1777835723903.png b/attached_assets/image_1777835723903.png deleted file mode 100644 index 5ef12388..00000000 Binary files a/attached_assets/image_1777835723903.png and /dev/null differ diff --git a/attached_assets/image_1777836094369.png b/attached_assets/image_1777836094369.png deleted file mode 100644 index e4ec34e9..00000000 Binary files a/attached_assets/image_1777836094369.png and /dev/null differ diff --git a/attached_assets/image_1777836870207.png b/attached_assets/image_1777836870207.png deleted file mode 100644 index 8f0c9f49..00000000 Binary files a/attached_assets/image_1777836870207.png and /dev/null differ diff --git a/attached_assets/image_1777876125208.png b/attached_assets/image_1777876125208.png deleted file mode 100644 index 79c29283..00000000 Binary files a/attached_assets/image_1777876125208.png and /dev/null differ diff --git a/attached_assets/image_1777877022157.png b/attached_assets/image_1777877022157.png deleted file mode 100644 index 6d95398e..00000000 Binary files a/attached_assets/image_1777877022157.png and /dev/null differ diff --git a/attached_assets/image_1777877772411.png b/attached_assets/image_1777877772411.png deleted file mode 100644 index cb85a7c9..00000000 Binary files a/attached_assets/image_1777877772411.png and /dev/null differ diff --git a/attached_assets/image_1777878671823.png b/attached_assets/image_1777878671823.png deleted file mode 100644 index f676bd4c..00000000 Binary files a/attached_assets/image_1777878671823.png and /dev/null differ diff --git a/attached_assets/image_1777880750966.png b/attached_assets/image_1777880750966.png deleted file mode 100644 index 9bf16201..00000000 Binary files a/attached_assets/image_1777880750966.png and /dev/null differ diff --git a/attached_assets/image_1777881965017.png b/attached_assets/image_1777881965017.png deleted file mode 100644 index c5404491..00000000 Binary files a/attached_assets/image_1777881965017.png and /dev/null differ diff --git a/attached_assets/image_1777892321385.png b/attached_assets/image_1777892321385.png deleted file mode 100644 index 44ba061f..00000000 Binary files a/attached_assets/image_1777892321385.png and /dev/null differ diff --git a/attached_assets/image_1777895733214.png b/attached_assets/image_1777895733214.png deleted file mode 100644 index ae6d9c5d..00000000 Binary files a/attached_assets/image_1777895733214.png and /dev/null differ diff --git a/attached_assets/image_1777899470333.png b/attached_assets/image_1777899470333.png deleted file mode 100644 index 78ea93bb..00000000 Binary files a/attached_assets/image_1777899470333.png and /dev/null differ diff --git a/attached_assets/image_1777899900497.png b/attached_assets/image_1777899900497.png deleted file mode 100644 index 996661be..00000000 Binary files a/attached_assets/image_1777899900497.png and /dev/null differ diff --git a/attached_assets/image_1777900205102.png b/attached_assets/image_1777900205102.png deleted file mode 100644 index e67da04a..00000000 Binary files a/attached_assets/image_1777900205102.png and /dev/null differ diff --git a/attached_assets/image_1777900351247.png b/attached_assets/image_1777900351247.png deleted file mode 100644 index 429ff897..00000000 Binary files a/attached_assets/image_1777900351247.png and /dev/null differ diff --git a/attached_assets/image_1777903518044.png b/attached_assets/image_1777903518044.png deleted file mode 100644 index cc10f660..00000000 Binary files a/attached_assets/image_1777903518044.png and /dev/null differ diff --git a/attached_assets/image_1777904088948.png b/attached_assets/image_1777904088948.png deleted file mode 100644 index 23436f62..00000000 Binary files a/attached_assets/image_1777904088948.png and /dev/null differ diff --git a/attached_assets/image_1777909686729.png b/attached_assets/image_1777909686729.png deleted file mode 100644 index 560d003c..00000000 Binary files a/attached_assets/image_1777909686729.png and /dev/null differ diff --git a/attached_assets/image_1777910388719.png b/attached_assets/image_1777910388719.png deleted file mode 100644 index 99354d1b..00000000 Binary files a/attached_assets/image_1777910388719.png and /dev/null differ diff --git a/attached_assets/image_1777910582739.png b/attached_assets/image_1777910582739.png deleted file mode 100644 index 2aa20c6b..00000000 Binary files a/attached_assets/image_1777910582739.png and /dev/null differ diff --git a/attached_assets/image_1777910744575.png b/attached_assets/image_1777910744575.png deleted file mode 100644 index 0abfe200..00000000 Binary files a/attached_assets/image_1777910744575.png and /dev/null differ diff --git a/attached_assets/image_1777910856262.png b/attached_assets/image_1777910856262.png deleted file mode 100644 index 73e69ce1..00000000 Binary files a/attached_assets/image_1777910856262.png and /dev/null differ diff --git a/attached_assets/image_1777910864208.png b/attached_assets/image_1777910864208.png deleted file mode 100644 index 6bea762d..00000000 Binary files a/attached_assets/image_1777910864208.png and /dev/null differ diff --git a/attached_assets/image_1777911113072.png b/attached_assets/image_1777911113072.png deleted file mode 100644 index b7f42fa3..00000000 Binary files a/attached_assets/image_1777911113072.png and /dev/null differ diff --git a/attached_assets/image_1777911528869.png b/attached_assets/image_1777911528869.png deleted file mode 100644 index bbad38f1..00000000 Binary files a/attached_assets/image_1777911528869.png and /dev/null differ diff --git a/attached_assets/image_1777916850996.png b/attached_assets/image_1777916850996.png deleted file mode 100644 index a75525ca..00000000 Binary files a/attached_assets/image_1777916850996.png and /dev/null differ diff --git a/attached_assets/image_1777917044588.png b/attached_assets/image_1777917044588.png deleted file mode 100644 index de226866..00000000 Binary files a/attached_assets/image_1777917044588.png and /dev/null differ diff --git a/attached_assets/image_1777921817832.png b/attached_assets/image_1777921817832.png deleted file mode 100644 index f69ed56d..00000000 Binary files a/attached_assets/image_1777921817832.png and /dev/null differ diff --git a/attached_assets/image_1777964908252.png b/attached_assets/image_1777964908252.png deleted file mode 100644 index 2bb58903..00000000 Binary files a/attached_assets/image_1777964908252.png and /dev/null differ diff --git a/attached_assets/image_1777965090828.png b/attached_assets/image_1777965090828.png deleted file mode 100644 index 1f06451f..00000000 Binary files a/attached_assets/image_1777965090828.png and /dev/null differ diff --git a/attached_assets/image_1777965189289.png b/attached_assets/image_1777965189289.png deleted file mode 100644 index 02d86e88..00000000 Binary files a/attached_assets/image_1777965189289.png and /dev/null differ diff --git a/attached_assets/image_1777965794791.png b/attached_assets/image_1777965794791.png deleted file mode 100644 index 0b7c4ada..00000000 Binary files a/attached_assets/image_1777965794791.png and /dev/null differ diff --git a/attached_assets/image_1777966241636.png b/attached_assets/image_1777966241636.png deleted file mode 100644 index 0b7c4ada..00000000 Binary files a/attached_assets/image_1777966241636.png and /dev/null differ diff --git a/attached_assets/image_1777966254291.png b/attached_assets/image_1777966254291.png deleted file mode 100644 index f1e777a0..00000000 Binary files a/attached_assets/image_1777966254291.png and /dev/null differ diff --git a/attached_assets/image_1777966779657.png b/attached_assets/image_1777966779657.png deleted file mode 100644 index b9fbb5af..00000000 Binary files a/attached_assets/image_1777966779657.png and /dev/null differ diff --git a/attached_assets/image_1777967058321.png b/attached_assets/image_1777967058321.png deleted file mode 100644 index 6adbeb7b..00000000 Binary files a/attached_assets/image_1777967058321.png and /dev/null differ diff --git a/attached_assets/image_1777967123954.png b/attached_assets/image_1777967123954.png deleted file mode 100644 index 0f014ddb..00000000 Binary files a/attached_assets/image_1777967123954.png and /dev/null differ diff --git a/attached_assets/image_1777967138468.png b/attached_assets/image_1777967138468.png deleted file mode 100644 index 0f014ddb..00000000 Binary files a/attached_assets/image_1777967138468.png and /dev/null differ diff --git a/attached_assets/image_1777967492557.png b/attached_assets/image_1777967492557.png deleted file mode 100644 index 939b2957..00000000 Binary files a/attached_assets/image_1777967492557.png and /dev/null differ diff --git a/attached_assets/image_1777974205084.png b/attached_assets/image_1777974205084.png deleted file mode 100644 index 1d7c6b10..00000000 Binary files a/attached_assets/image_1777974205084.png and /dev/null differ diff --git a/attached_assets/image_1777987277760.png b/attached_assets/image_1777987277760.png deleted file mode 100644 index 7d552279..00000000 Binary files a/attached_assets/image_1777987277760.png and /dev/null differ diff --git a/attached_assets/image_1777990601928.png b/attached_assets/image_1777990601928.png deleted file mode 100644 index d899a8f0..00000000 Binary files a/attached_assets/image_1777990601928.png and /dev/null differ diff --git a/attached_assets/image_1777990936855.png b/attached_assets/image_1777990936855.png deleted file mode 100644 index d4e792f5..00000000 Binary files a/attached_assets/image_1777990936855.png and /dev/null differ diff --git a/attached_assets/image_1778050241113.png b/attached_assets/image_1778050241113.png deleted file mode 100644 index 80d70cc4..00000000 Binary files a/attached_assets/image_1778050241113.png and /dev/null differ diff --git a/attached_assets/image_1778055174519.png b/attached_assets/image_1778055174519.png deleted file mode 100644 index aa8d9f1b..00000000 Binary files a/attached_assets/image_1778055174519.png and /dev/null differ diff --git a/attached_assets/image_1778059367890.png b/attached_assets/image_1778059367890.png deleted file mode 100644 index 1f38f3e0..00000000 Binary files a/attached_assets/image_1778059367890.png and /dev/null differ diff --git a/attached_assets/image_1778062557633.png b/attached_assets/image_1778062557633.png deleted file mode 100644 index 877bb22c..00000000 Binary files a/attached_assets/image_1778062557633.png and /dev/null differ diff --git a/attached_assets/image_1778082909789.png b/attached_assets/image_1778082909789.png deleted file mode 100644 index c6f07881..00000000 Binary files a/attached_assets/image_1778082909789.png and /dev/null differ diff --git a/attached_assets/image_1778139444852.png b/attached_assets/image_1778139444852.png deleted file mode 100644 index 4c72eb66..00000000 Binary files a/attached_assets/image_1778139444852.png and /dev/null differ diff --git a/attached_assets/image_1778139463711.png b/attached_assets/image_1778139463711.png deleted file mode 100644 index ce14389a..00000000 Binary files a/attached_assets/image_1778139463711.png and /dev/null differ diff --git a/attached_assets/image_1778143021708.png b/attached_assets/image_1778143021708.png deleted file mode 100644 index 97799f85..00000000 Binary files a/attached_assets/image_1778143021708.png and /dev/null differ diff --git a/attached_assets/image_1778143890472.png b/attached_assets/image_1778143890472.png deleted file mode 100644 index 59f97295..00000000 Binary files a/attached_assets/image_1778143890472.png and /dev/null differ diff --git a/attached_assets/image_1778144755695.png b/attached_assets/image_1778144755695.png deleted file mode 100644 index 88d89a95..00000000 Binary files a/attached_assets/image_1778144755695.png and /dev/null differ diff --git a/attached_assets/image_1778148019333.png b/attached_assets/image_1778148019333.png deleted file mode 100644 index 388b2931..00000000 Binary files a/attached_assets/image_1778148019333.png and /dev/null differ diff --git a/attached_assets/image_1778148516608.png b/attached_assets/image_1778148516608.png deleted file mode 100644 index 510d8c8c..00000000 Binary files a/attached_assets/image_1778148516608.png and /dev/null differ diff --git a/attached_assets/image_1778165675783.png b/attached_assets/image_1778165675783.png deleted file mode 100644 index f198a278..00000000 Binary files a/attached_assets/image_1778165675783.png and /dev/null differ diff --git a/attached_assets/image_1778166558186.png b/attached_assets/image_1778166558186.png deleted file mode 100644 index 1daacf50..00000000 Binary files a/attached_assets/image_1778166558186.png and /dev/null differ diff --git a/attached_assets/image_1778322994442.png b/attached_assets/image_1778322994442.png deleted file mode 100644 index eda5f08f..00000000 Binary files a/attached_assets/image_1778322994442.png and /dev/null differ diff --git a/attached_assets/image_1778397709349.png b/attached_assets/image_1778397709349.png deleted file mode 100644 index 9833375f..00000000 Binary files a/attached_assets/image_1778397709349.png and /dev/null differ diff --git a/attached_assets/image_1778397731390.png b/attached_assets/image_1778397731390.png deleted file mode 100644 index d0d8febb..00000000 Binary files a/attached_assets/image_1778397731390.png and /dev/null differ diff --git a/attached_assets/image_1778398164373.png b/attached_assets/image_1778398164373.png deleted file mode 100644 index 2047ef01..00000000 Binary files a/attached_assets/image_1778398164373.png and /dev/null differ diff --git a/attached_assets/image_1778403149119.png b/attached_assets/image_1778403149119.png deleted file mode 100644 index 25cc7b68..00000000 Binary files a/attached_assets/image_1778403149119.png and /dev/null differ diff --git a/attached_assets/image_1778405255399.png b/attached_assets/image_1778405255399.png deleted file mode 100644 index cba18710..00000000 Binary files a/attached_assets/image_1778405255399.png and /dev/null differ diff --git a/attached_assets/image_1778405781740.png b/attached_assets/image_1778405781740.png deleted file mode 100644 index a0951b2b..00000000 Binary files a/attached_assets/image_1778405781740.png and /dev/null differ diff --git a/attached_assets/image_1778410143481.png b/attached_assets/image_1778410143481.png deleted file mode 100644 index 94588375..00000000 Binary files a/attached_assets/image_1778410143481.png and /dev/null differ diff --git a/attached_assets/image_1778420509847.png b/attached_assets/image_1778420509847.png deleted file mode 100644 index aac0124f..00000000 Binary files a/attached_assets/image_1778420509847.png and /dev/null differ diff --git a/attached_assets/image_1778421194263.png b/attached_assets/image_1778421194263.png deleted file mode 100644 index 547bbadc..00000000 Binary files a/attached_assets/image_1778421194263.png and /dev/null differ diff --git a/attached_assets/image_1778421369112.png b/attached_assets/image_1778421369112.png deleted file mode 100644 index 1d620665..00000000 Binary files a/attached_assets/image_1778421369112.png and /dev/null differ diff --git a/attached_assets/image_1778421799153.png b/attached_assets/image_1778421799153.png deleted file mode 100644 index c98c8267..00000000 Binary files a/attached_assets/image_1778421799153.png and /dev/null differ diff --git a/attached_assets/image_1778422139550.png b/attached_assets/image_1778422139550.png deleted file mode 100644 index e6a3ec0f..00000000 Binary files a/attached_assets/image_1778422139550.png and /dev/null differ diff --git a/attached_assets/image_1778422163618.png b/attached_assets/image_1778422163618.png deleted file mode 100644 index e6a3ec0f..00000000 Binary files a/attached_assets/image_1778422163618.png and /dev/null differ diff --git a/attached_assets/image_1778422720285.png b/attached_assets/image_1778422720285.png deleted file mode 100644 index 53261c99..00000000 Binary files a/attached_assets/image_1778422720285.png and /dev/null differ diff --git a/attached_assets/image_1778423923662.png b/attached_assets/image_1778423923662.png deleted file mode 100644 index 5201a97d..00000000 Binary files a/attached_assets/image_1778423923662.png and /dev/null differ diff --git a/attached_assets/image_1778426455919.png b/attached_assets/image_1778426455919.png deleted file mode 100644 index 42f0120e..00000000 Binary files a/attached_assets/image_1778426455919.png and /dev/null differ diff --git a/attached_assets/image_1778426720288.png b/attached_assets/image_1778426720288.png deleted file mode 100644 index 781940b8..00000000 Binary files a/attached_assets/image_1778426720288.png and /dev/null differ diff --git a/attached_assets/image_1778427034490.png b/attached_assets/image_1778427034490.png deleted file mode 100644 index 0b7bcf63..00000000 Binary files a/attached_assets/image_1778427034490.png and /dev/null differ diff --git a/attached_assets/image_1778427099618.png b/attached_assets/image_1778427099618.png deleted file mode 100644 index 2be14a65..00000000 Binary files a/attached_assets/image_1778427099618.png and /dev/null differ diff --git a/attached_assets/image_1778427980756.png b/attached_assets/image_1778427980756.png deleted file mode 100644 index 2a090e12..00000000 Binary files a/attached_assets/image_1778427980756.png and /dev/null differ diff --git a/attached_assets/image_1778486052925.png b/attached_assets/image_1778486052925.png deleted file mode 100644 index 94942c44..00000000 Binary files a/attached_assets/image_1778486052925.png and /dev/null differ diff --git a/attached_assets/image_1778486079501.png b/attached_assets/image_1778486079501.png deleted file mode 100644 index 75620450..00000000 Binary files a/attached_assets/image_1778486079501.png and /dev/null differ diff --git a/attached_assets/image_1778486175469.png b/attached_assets/image_1778486175469.png deleted file mode 100644 index 14c7b180..00000000 Binary files a/attached_assets/image_1778486175469.png and /dev/null differ diff --git a/attached_assets/image_1778486259605.png b/attached_assets/image_1778486259605.png deleted file mode 100644 index 89bc3f75..00000000 Binary files a/attached_assets/image_1778486259605.png and /dev/null differ diff --git a/attached_assets/image_1778494045234.png b/attached_assets/image_1778494045234.png deleted file mode 100644 index 00798672..00000000 Binary files a/attached_assets/image_1778494045234.png and /dev/null differ diff --git a/attached_assets/image_1778494368964.png b/attached_assets/image_1778494368964.png deleted file mode 100644 index 8eef96a9..00000000 Binary files a/attached_assets/image_1778494368964.png and /dev/null differ diff --git a/attached_assets/image_1778506201884.png b/attached_assets/image_1778506201884.png deleted file mode 100644 index b68d9189..00000000 Binary files a/attached_assets/image_1778506201884.png and /dev/null differ diff --git a/attached_assets/image_1778509797292.png b/attached_assets/image_1778509797292.png deleted file mode 100644 index 32472c52..00000000 Binary files a/attached_assets/image_1778509797292.png and /dev/null differ diff --git a/attached_assets/image_1778509849923.png b/attached_assets/image_1778509849923.png deleted file mode 100644 index c58cb128..00000000 Binary files a/attached_assets/image_1778509849923.png and /dev/null differ diff --git a/attached_assets/image_1778511624937.png b/attached_assets/image_1778511624937.png deleted file mode 100644 index a937490c..00000000 Binary files a/attached_assets/image_1778511624937.png and /dev/null differ diff --git a/attached_assets/image_1778513200267.png b/attached_assets/image_1778513200267.png deleted file mode 100644 index f3187aef..00000000 Binary files a/attached_assets/image_1778513200267.png and /dev/null differ diff --git a/attached_assets/image_1778514333365.png b/attached_assets/image_1778514333365.png deleted file mode 100644 index 5ea4aa20..00000000 Binary files a/attached_assets/image_1778514333365.png and /dev/null differ diff --git a/attached_assets/image_1778523772982.png b/attached_assets/image_1778523772982.png deleted file mode 100644 index ee9284bd..00000000 Binary files a/attached_assets/image_1778523772982.png and /dev/null differ diff --git a/attached_assets/image_1778572173140.png b/attached_assets/image_1778572173140.png deleted file mode 100644 index 6b038d94..00000000 Binary files a/attached_assets/image_1778572173140.png and /dev/null differ diff --git a/attached_assets/image_1778576249243.png b/attached_assets/image_1778576249243.png deleted file mode 100644 index 2ddd1dc4..00000000 Binary files a/attached_assets/image_1778576249243.png and /dev/null differ diff --git a/attached_assets/image_1778576626152.png b/attached_assets/image_1778576626152.png deleted file mode 100644 index 7823e64f..00000000 Binary files a/attached_assets/image_1778576626152.png and /dev/null differ diff --git a/attached_assets/image_1778577193212.png b/attached_assets/image_1778577193212.png deleted file mode 100644 index 034a30ed..00000000 Binary files a/attached_assets/image_1778577193212.png and /dev/null differ diff --git a/attached_assets/image_1778577898945.png b/attached_assets/image_1778577898945.png deleted file mode 100644 index bec50758..00000000 Binary files a/attached_assets/image_1778577898945.png and /dev/null differ diff --git a/attached_assets/image_1778578308240.png b/attached_assets/image_1778578308240.png deleted file mode 100644 index 4b8db3ad..00000000 Binary files a/attached_assets/image_1778578308240.png and /dev/null differ diff --git a/attached_assets/image_1778579111549.png b/attached_assets/image_1778579111549.png deleted file mode 100644 index fcee91a8..00000000 Binary files a/attached_assets/image_1778579111549.png and /dev/null differ diff --git a/attached_assets/image_1778579324630.png b/attached_assets/image_1778579324630.png deleted file mode 100644 index a1a4831b..00000000 Binary files a/attached_assets/image_1778579324630.png and /dev/null differ diff --git a/attached_assets/image_1778581542354.png b/attached_assets/image_1778581542354.png deleted file mode 100644 index 386ceadb..00000000 Binary files a/attached_assets/image_1778581542354.png and /dev/null differ diff --git a/attached_assets/image_1778582606077.png b/attached_assets/image_1778582606077.png deleted file mode 100644 index 29a928ce..00000000 Binary files a/attached_assets/image_1778582606077.png and /dev/null differ diff --git a/attached_assets/image_1778584487739.png b/attached_assets/image_1778584487739.png deleted file mode 100644 index 082f2b8e..00000000 Binary files a/attached_assets/image_1778584487739.png and /dev/null differ diff --git a/attached_assets/image_1778586432383.png b/attached_assets/image_1778586432383.png deleted file mode 100644 index a044a03b..00000000 Binary files a/attached_assets/image_1778586432383.png and /dev/null differ diff --git a/attached_assets/image_1778587089221.png b/attached_assets/image_1778587089221.png deleted file mode 100644 index 423ed037..00000000 Binary files a/attached_assets/image_1778587089221.png and /dev/null differ diff --git a/attached_assets/image_1778654556750.png b/attached_assets/image_1778654556750.png deleted file mode 100644 index 8a17c7c5..00000000 Binary files a/attached_assets/image_1778654556750.png and /dev/null differ diff --git a/attached_assets/image_1778655092243.png b/attached_assets/image_1778655092243.png deleted file mode 100644 index 12736924..00000000 Binary files a/attached_assets/image_1778655092243.png and /dev/null differ diff --git a/attached_assets/image_1778655101739.png b/attached_assets/image_1778655101739.png deleted file mode 100644 index 12736924..00000000 Binary files a/attached_assets/image_1778655101739.png and /dev/null differ diff --git a/attached_assets/pdf-checks/arabic-pdf-render.png b/attached_assets/pdf-checks/arabic-pdf-render.png deleted file mode 100644 index 4721f06f..00000000 Binary files a/attached_assets/pdf-checks/arabic-pdf-render.png and /dev/null differ diff --git a/attached_assets/rrr1_1777817836714.pdf b/attached_assets/rrr1_1777817836714.pdf deleted file mode 100644 index 1d703bf3..00000000 Binary files a/attached_assets/rrr1_1777817836714.pdf and /dev/null differ diff --git a/attached_assets/rrr1_1777835123297.pdf b/attached_assets/rrr1_1777835123297.pdf deleted file mode 100644 index 1d703bf3..00000000 Binary files a/attached_assets/rrr1_1777835123297.pdf and /dev/null differ diff --git a/attached_assets/rrr1_1777879598338.pdf b/attached_assets/rrr1_1777879598338.pdf deleted file mode 100644 index 1d703bf3..00000000 Binary files a/attached_assets/rrr1_1777879598338.pdf and /dev/null differ diff --git a/attached_assets/tx-icon/favicon.svg b/attached_assets/tx-icon/favicon.svg deleted file mode 100644 index 6095adfc..00000000 --- a/attached_assets/tx-icon/favicon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/attached_assets/tx-icon/main-transparent.svg b/attached_assets/tx-icon/main-transparent.svg deleted file mode 100644 index b8a3915a..00000000 --- a/attached_assets/tx-icon/main-transparent.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/attached_assets/tx-icon/main.svg b/attached_assets/tx-icon/main.svg deleted file mode 100644 index 55716e7e..00000000 --- a/attached_assets/tx-icon/main.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/attached_assets/tx-icon/preview.html b/attached_assets/tx-icon/preview.html deleted file mode 100644 index 46de395e..00000000 --- a/attached_assets/tx-icon/preview.html +++ /dev/null @@ -1,43 +0,0 @@ - -Tx icon preview - - -

Tx icon — sizes & variants

-

Variant 1 — Main (T with X-feet) — RECOMMENDED

-
-
16
-
32
-
64
-
128
-
-

Variant 2 — TX overlapped (X under, T over)

-
-
16
-
32
-
64
-
128
-
-

Variant 3 — Minimal T + accent dot

-
-
16
-
32
-
64
-
128
-
-

Transparent (works on light & dark)

-
-
light bg
-
dark bg
-
-

Favicon-optimized (32x32 viewBox, thicker strokes)

-
-
16
-
32
-
- diff --git a/attached_assets/tx-icon/variant-2.svg b/attached_assets/tx-icon/variant-2.svg deleted file mode 100644 index 9beaed01..00000000 --- a/attached_assets/tx-icon/variant-2.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/attached_assets/tx-icon/variant-3.svg b/attached_assets/tx-icon/variant-3.svg deleted file mode 100644 index 80d5a4ec..00000000 --- a/attached_assets/tx-icon/variant-3.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/attached_assets/قائمة_الاجتماعات_يوم_الثلاثاء_(3)_1777966694535.pdf b/attached_assets/قائمة_الاجتماعات_يوم_الثلاثاء_(3)_1777966694535.pdf deleted file mode 100644 index c06fa382..00000000 Binary files a/attached_assets/قائمة_الاجتماعات_يوم_الثلاثاء_(3)_1777966694535.pdf and /dev/null differ diff --git a/attached_assets/نموذج_الجدول_1777290182495.png b/attached_assets/نموذج_الجدول_1777290182495.png deleted file mode 100644 index 4ff5be4f..00000000 Binary files a/attached_assets/نموذج_الجدول_1777290182495.png and /dev/null differ 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.