diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..36eb41a --- /dev/null +++ b/.env.example @@ -0,0 +1,53 @@ +# Copy to .env (read by Next.js, the Prisma CLI and scripts) and fill in. Never commit the real file. +# Only DATABASE_URL, DIRECT_URL and the two JWT secrets are required; everything else degrades gracefully. + +# --- Database: Neon Postgres -------------------------------------------------------------------- +# Neon console → Connect → enable "Connection pooling" for the pooled URL (host contains "-pooler"). +# The app uses the pooled URL at runtime; migrations use the direct (unpooled) one. +DATABASE_URL="postgresql://USER:PASSWORD@ep-xxxx-pooler.REGION.aws.neon.tech/neondb?sslmode=require" +DIRECT_URL="postgresql://USER:PASSWORD@ep-xxxx.REGION.aws.neon.tech/neondb?sslmode=require" +# Only needed for `prisma migrate dev` / drift checks when you can't let Prisma create one +# (e.g. a dedicated Neon branch such as "shadow"). +# SHADOW_DATABASE_URL="postgresql://USER:PASSWORD@ep-shadow.REGION.aws.neon.tech/neondb?sslmode=require" +# Driver: "neon" (serverless driver, default for *.neon.tech) or "pg" (node-postgres, default otherwise). +# DATABASE_DRIVER=neon + +# Public base URL (used for Google OAuth callback and password-reset links) +APP_URL="http://localhost:3000" + +# --- Auth ------------------------------------------------------------------------------------------ +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))" +JWT_SECRET="" +JWT_REFRESH_SECRET="" + +# Google OAuth 2.0 (authorised redirect URI: ${APP_URL}/api/auth/google/callback) +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET="" + +# --- Integrations ----------------------------------------------------------------------------------- +GOOGLE_MAPS_API_KEY="" + +# Firebase Realtime Database (GPS ingestion). base64 of the service-account JSON: +# base64 -w0 firebase-service-account.json +FIREBASE_CREDENTIALS_BASE64="" +FIREBASE_DATABASE_URL="https://matatu-eta-default-rtdb.europe-west1.firebasedatabase.app/" + +# Africa's Talking SMS ("sandbox" uses the sandbox API) +AFRICAS_TALKING_API_KEY="" +AFRICAS_TALKING_USERNAME="sandbox" + +# SMTP for password-reset emails (without these, dev uses an Ethereal test inbox) +EMAIL_HOST="smtp.gmail.com" +EMAIL_PORT=465 +EMAIL_SECURE=true +EMAIL_USER="" +EMAIL_PASS="" +EMAIL_FROM="" + +# --- Background ETA cycle ------------------------------------------------------------------------------ +# interval: run every 30 s inside `next start` (default when not on Vercel) +# cron: a scheduler calls /api/cron/eta-cycle (default on Vercel, see vercel.json) +# off: disabled (e.g. when running `npm run eta:worker` separately) +# ETA_CYCLE_MODE=interval +# Required for /api/cron/eta-cycle (Vercel sends it as `Authorization: Bearer `) +CRON_SECRET="" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..bd412f4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# Every schema change needs a maintainer's review (docs/MIGRATIONS.md). +/prisma/ @cliffkoome +/docs/MIGRATIONS.md @cliffkoome +/.github/workflows/ @cliffkoome diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31b3a32 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,98 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + NEXT_TELEMETRY_DISABLED: 1 + +jobs: + checks: + name: Lint, typecheck, unit tests, build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - name: Validate Prisma schema + run: npm run db:validate + - run: npm run lint + - run: npm run typecheck + - run: npm test + - name: Build (no database needed) + run: npm run build + + migrations: + name: Migrations (rules, drift, apply, integration tests) + runs-on: ubuntu-latest + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: matconnect + ports: ["5432:5432"] + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/matconnect + DIRECT_URL: postgresql://postgres:postgres@localhost:5432/matconnect + SHADOW_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/matconnect_shadow + JWT_SECRET: ci-access-secret-0123456789abcdefghijklmnop + JWT_REFRESH_SECRET: ci-refresh-secret-0123456789abcdefghijklmnop + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # the rule checker diffs against the base branch + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + + - name: Create shadow database + run: PGPASSWORD=postgres psql -h localhost -U postgres -c 'CREATE DATABASE matconnect_shadow' + + - name: Migration rules (immutability, ordering, destructive SQL) + env: + # PRs compare with their base branch; pushes to main compare with the previous commit. + MIGRATIONS_BASE_REF: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || github.event.before }} + run: npm run db:migrate:check + + - name: No drift between schema.prisma and the migrations + run: | + if ! npx prisma migrate diff --from-migrations prisma/migrations --to-schema prisma/schema.prisma --exit-code --script; then + echo "::error::schema.prisma and prisma/migrations disagree. Run 'npm run db:migrate:create' and commit the migration." + exit 1 + fi + + - name: Apply the full history to an empty database + run: npx prisma migrate deploy + + - name: Migrated database matches schema.prisma + run: npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --exit-code + + - name: Re-running deploy is a no-op + run: npx prisma migrate deploy + + - name: Integration tests (real Postgres) + run: npm run test:integration + + - name: Seed script works + run: npm run db:seed diff --git a/.github/workflows/deploy-migrations.yml b/.github/workflows/deploy-migrations.yml new file mode 100644 index 0000000..444cf41 --- /dev/null +++ b/.github/workflows/deploy-migrations.yml @@ -0,0 +1,51 @@ +name: Deploy migrations + +# The ONLY path by which schema changes reach production (docs/MIGRATIONS.md, rule R3). + +on: + push: + branches: [main] + paths: + - "prisma/migrations/**" + - "prisma/schema.prisma" + workflow_dispatch: + +# Never run two production migrations at once, and never cancel one half-way. +concurrency: + group: deploy-migrations-production + cancel-in-progress: false + +permissions: + contents: read + +jobs: + deploy: + name: prisma migrate deploy → Neon production + runs-on: ubuntu-latest + environment: production # add required reviewers in Settings → Environments to gate this + env: + # Neon *direct* (unpooled) connection string of the production branch. + DIRECT_URL: ${{ secrets.DATABASE_URL_DIRECT_PRODUCTION }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + + - name: Check configuration + run: | + if [ -z "$DIRECT_URL" ]; then + echo "::error::Set the DATABASE_URL_DIRECT_PRODUCTION secret (Neon direct connection string)." + exit 1 + fi + + - name: Pending migrations + run: npx prisma migrate status || true + + - name: Apply migrations + run: npx prisma migrate deploy + + - name: Production matches schema.prisma + run: npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --exit-code diff --git a/.github/workflows/neon-preview.yml b/.github/workflows/neon-preview.yml new file mode 100644 index 0000000..cdbc1ca --- /dev/null +++ b/.github/workflows/neon-preview.yml @@ -0,0 +1,69 @@ +name: Neon preview branch + +# For PRs that change the database: copy production into a Neon branch (copy-on-write, seconds) and +# apply the PR's migrations to it, so migrations are tested against real data before merge. +# Does nothing until the NEON_PROJECT_ID variable and NEON_API_KEY secret are configured. + +on: + pull_request: + types: [opened, reopened, synchronize, closed] + paths: + - "prisma/**" + +concurrency: + group: neon-preview-${{ github.event.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + migrate-preview: + if: github.event.action != 'closed' && vars.NEON_PROJECT_ID != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + + # Start from a fresh copy of production on every push. + - name: Remove previous preview branch + continue-on-error: true + uses: neondatabase/delete-branch-action@v3 + with: + project_id: ${{ vars.NEON_PROJECT_ID }} + branch: preview/pr-${{ github.event.number }} + api_key: ${{ secrets.NEON_API_KEY }} + + - name: Create preview branch from production + id: branch + uses: neondatabase/create-branch-action@v5 + with: + project_id: ${{ vars.NEON_PROJECT_ID }} + branch_name: preview/pr-${{ github.event.number }} + username: ${{ vars.NEON_DB_USER || 'neondb_owner' }} + database: ${{ vars.NEON_DB_NAME || 'neondb' }} + api_key: ${{ secrets.NEON_API_KEY }} + + - name: Apply this PR's migrations to the copy + env: + DIRECT_URL: ${{ steps.branch.outputs.db_url }} + run: npx prisma migrate deploy + + - name: Result matches schema.prisma + env: + DIRECT_URL: ${{ steps.branch.outputs.db_url }} + run: npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --exit-code + + cleanup: + if: github.event.action == 'closed' && vars.NEON_PROJECT_ID != '' + runs-on: ubuntu-latest + steps: + - uses: neondatabase/delete-branch-action@v3 + with: + project_id: ${{ vars.NEON_PROJECT_ID }} + branch: preview/pr-${{ github.event.number }} + api_key: ${{ secrets.NEON_API_KEY }} diff --git a/.gitignore b/.gitignore index a5f2540..4c1ebf2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,27 @@ +# dependencies /node_modules + +# Next.js +/.next/ +/out/ +next-env.d.ts +*.tsbuildinfo + +# Prisma client (generated by `prisma generate` / postinstall) +/src/generated/ + +# env files — commit only the example .env +.env.* +!.env.example + +# testing +/coverage + +# misc +.DS_Store +*.pem +npm-debug.log* +.vercel requests.rest -server/config/firebase-service-account.json -/tests -repomix* \ No newline at end of file +repomix* diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index aef8443..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file diff --git a/README.md b/README.md index 24a493a..4c3026d 100644 --- a/README.md +++ b/README.md @@ -1,200 +1,118 @@ -# MatConnect: IoT-Enabled Real-time Matatu Arrival Display - -**MatConnect** is a full-stack web application designed to revolutionize the public transport experience in Kenya by providing real-time arrival information for matatus (public minibuses). By leveraging IoT GPS devices, this platform offers passengers accurate ETAs, reducing uncertainty and wait times at various stages. The system also includes robust administrative dashboards for system, matatu, and vehicle management. - ---- - -## Table of Contents - -- [Overview](#overview) -- [Key Features](#key-features) - - [Passenger Portal](#passenger-portal) - - [Admin Dashboard](#admin-dashboard) - - [Matatu Admin Dashboard](#matatu-admin-dashboard) -- [System Architecture](#system-architecture) -- [Core Business Logic](#core-business-logic) - - [Real-time ETA Calculation](#real-time-eta-calculation) - - [SMS Arrival Alerts](#sms-arrival-alerts) - - [Role-Based Access Control (RBAC)](#role-based-access-control-rbac) -- [Technology Stack](#technology-stack) -- [Project Setup](#project-setup) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Environment Configuration](#environment-configuration) - - [Running the Application](#running-the-application) -- [API Endpoints](#api-endpoints) - ---- - -## Overview - -The daily commute for many involves long, unpredictable waits for matatus. MatConnect addresses this by tracking GPS-enabled vehicles and calculating their real-time Estimated Time of Arrival (ETA) to designated stages. - -The platform consists of three main components: -1. **A Public-Facing Passenger Portal:** Allows users to view live ETAs for vehicles approaching their selected stage and subscribe to SMS alerts. -2. **A System Administrator Dashboard:** A comprehensive control panel for managing all aspects of the system, including users, vehicles, and stages. -3. **A Matatu Admin Dashboard:** A dedicated view for vehicle owners to monitor their fleet's performance. - -## Key Features - -### Passenger Portal - -- **Real-time ETA Display:** Select a stage and view a live list of approaching matatus with their ETAs. -- **User Authentication:** Secure sign-up and login using email/password or Google OAuth 2.0. -- **Two-Factor Authentication (2FA):** Enhanced account security using TOTP (e.g., Google Authenticator). -- **SMS Arrival Alerts:** Subscribe to a stage to receive an SMS notification when a matatu arrives. -- **Feedback System:** Rate and comment on vehicle rides to provide valuable feedback. -- **Profile Management:** Update personal details, including the phone number required for SMS alerts. - -### Admin Dashboard - -- **Live System Overview:** A central dashboard displaying key metrics like total vehicles, online vehicles, and configured stages. -- **Live Vehicle Tracking:** A table showing the real-time status and last known location of all vehicles. -- **Comprehensive User Management:** - - View, search (by name/email), and filter all system users (Admins, MatAdmins, Passengers). - - Create new Admin or MatAdmin accounts. - - Block, unblock, or permanently delete any user. -- **Stage & Vehicle Management:** - - Create, view, and manage stages with their geographic coordinates. - - Create new vehicles and assign them to MatAdmin owners. - - Assign and unassign vehicles to/from stages to build routes. -- **Feedback Monitoring:** View a stream of the latest passenger feedback. - -### Matatu Admin Dashboard - -- **Fleet Overview:** A dedicated dashboard for vehicle owners to view and manage their assigned vehicles. -- **Performance Metrics:** (Future Scope) Track daily trips, distance covered, and other performance indicators. - -## System Architecture - -MatConnect is built on a modern, decoupled architecture: - -- **Frontend:** A client-side application built with **HTML5, CSS3, and Vanilla JavaScript**. It communicates with the backend via a RESTful API. -- **Backend:** A **Node.js** server using the **Express.js** framework. It handles business logic, authentication, and data processing. -- **Database:** A **MariaDB** relational database, with **Sequelize** as the ORM for robust and safe data modeling and querying. -- **Real-time Data Ingestion:** **Firebase Realtime Database** acts as the ingestion point for live GPS coordinates sent from IoT devices installed in the matatus. -- **External Services:** - - **Google Maps API:** Used for `Directions` (to calculate ETAs considering traffic) and `Geocoding` (to convert coordinates into human-readable addresses). - - **Google OAuth 2.0:** For secure and convenient user authentication. - - **Africa's Talking API:** For dispatching SMS arrival alerts to subscribed passengers. - -## Core Business Logic - -### Real-time ETA Calculation - -The ETA calculation is handled by a background service (`etaCalculationService.js`) that runs every 30 seconds. - -1. The service fetches all stages and their assigned vehicles from the MariaDB database. -2. For each vehicle, it retrieves the latest GPS location from the **Firebase Realtime Database**. -3. If a vehicle is online, it uses the **Google Maps Directions API** to calculate the travel duration from the vehicle's current location to the stage's location. The `departure_time: 'now'` parameter is used to get traffic-aware ETAs. -4. The distance to the stage is checked. If a vehicle is within a 50-meter radius, its status is marked as **'Arrived'**. Otherwise, it's **'Approaching'**. -5. The calculated ETAs and statuses are then pushed to a dedicated `etas` node in Firebase, which the frontend clients listen to for live updates. - -### SMS Arrival Alerts - -1. An authenticated passenger can subscribe to receive an alert for a specific stage. This requires them to have a verified phone number in their profile. -2. When the background ETA service marks a vehicle's status as **'Arrived'**, it queries the database for all users subscribed to that stage. -3. It iterates through the subscribers and sends an SMS notification via the **Africa's Talking API**. -4. To prevent spam, a user is **automatically unsubscribed** from the stage immediately after receiving an alert. - -### Role-Based Access Control (RBAC) - -The system uses JSON Web Tokens (JWT) for authentication. Upon login, a user's role (`Admin`, `MatAdmin`, or `Passenger`) is encoded into the JWT. - -A custom middleware (`authMiddleware.js`) protects backend routes by verifying the JWT and checking if the user's role has the necessary permissions for the requested resource. This ensures a secure separation of functionalities. - -## Technology Stack - -- **Backend:** Node.js, Express.js, Sequelize, Passport.js (for Google OAuth) -- **Frontend:** HTML5, CSS3, Vanilla JavaScript -- **Database:** MariaDB, Firebase Realtime Database -- **APIs & Services:** Google Maps API, Google OAuth, Africa's Talking API -- **Authentication:** JWT, bcrypt (for password hashing), Speakeasy (for 2FA) - -## Project Setup - -### Prerequisites - -- Node.js (v18.x or later) -- MariaDB (or MySQL) -- An account for Firebase, Google Cloud Platform, and Africa's Talking. - -### Installation - -1. **Clone the repository:** - ```bash - git clone https://github.com/cliffkoome/MatConnect - cd GRP-A-ISP-cliffkoome - ``` - -2. **Install server dependencies:** - ```bash - cd server - npm install - ``` - -### Environment Configuration - -1. In the `server/` directory, create a `.env` file by copying the `.env.example` file. - -2. Fill in the required credentials in your `.env` file: - ```env - # Server - PORT=5000 - SERVER_BASE_URL=http://localhost:5000 - - # Database (MariaDB/MySQL) - DB_HOST=localhost - DB_USER=your_db_user - DB_PASSWORD=your_db_password - DB_NAME=matconnect_db - - # JWT Secrets - JWT_SECRET=your_super_secret_key - JWT_REFRESH_SECRET=your_super_secret_refresh_key - - # Google OAuth 2.0 - GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com - GOOGLE_CLIENT_SECRET=your_google_client_secret - - # Google Maps API - GOOGLE_MAPS_API_KEY=your_google_maps_api_key - - # Africa's Talking API - AFRICAS_TALKING_API_KEY=your_africastalking_api_key - AFRICAS_TALKING_USERNAME=sandbox # or your live username - - # Email for Password Resets (e.g., using Gmail App Password) - EMAIL_HOST=smtp.gmail.com - EMAIL_PORT=465 - EMAIL_SECURE=true - EMAIL_USER=your_email@gmail.com - EMAIL_PASS=your_gmail_app_password - ``` - -3. **Firebase Setup:** - - Create a Firebase project and set up the **Realtime Database**. - - Generate a service account key JSON file. - - Place the file in `server/config/` and rename it to `firebase-service-account.json`. - -### Running the Application - -1. **Start the server:** - From the `server/` directory, run: - ```bash - npm start - ``` - This will start the server (usually on `http://localhost:5000`) and automatically synchronize the database schema. - -2. **Access the application:** - Open your web browser and navigate to `http://localhost:5000`. - -## API Endpoints - -The API is organized by resource and protected by role-based authentication. - -- `/api/auth/`: User registration, login (email/Google), 2FA, profile updates. -- `/api/admin/`: Admin-only endpoints for managing users, stages, vehicles, and viewing system data. -- `/api/stages/`: Public endpoints for fetching stages and managing SMS subscriptions. -- `/api/eta/`: Endpoints for fetching live ETA data for a specific stage. -- `/api/feedback/`: Endpoints for submitting and retrieving feedback. +# MatConnect — real-time matatu arrivals + +MatConnect shows passengers live ETAs for matatus approaching their stage, sends one-shot SMS alerts +when a matatu arrives, and gives system admins and matatu owners dashboards for stages, vehicles, +users, trips, distance and feedback. GPS devices in the vehicles push fixes to Firebase; a background +cycle turns them into ETAs. + +**Stack:** Next.js 16 (App Router) · TypeScript · Prisma 7 · PostgreSQL on Neon · Vitest + +## Features + +- **Passengers** — pick a stage, see live ETAs (refreshes every 30 s), subscribe to an SMS alert for the + next arrival, rate rides, manage profile and TOTP two-factor auth, sign in with email or Google. +- **Admins** — live fleet status with street-level locations, stage & route management, vehicle + registry, user management (search, filter, block, delete, create admins), feedback feed. +- **Matatu admins** — today's fleet totals, per-vehicle distance/trips for any day, daily/weekly/monthly + charts (PNG export) and a CSV fleet report. + +## Architecture + +``` +src/ +├─ app/ Next.js routes +│ ├─ api/**/route.ts HTTP layer only: validate → call a service → JSON +│ └─ (pages) React client pages (/, /login, /stages, /admin, /mat-admin, …) +├─ server/ server-only code (never imported by client components — lint-enforced) +│ ├─ http/ route() wrapper, error mapping, rate limiting +│ ├─ auth/ JWTs (jose), bcrypt, TOTP, guard, cookies, Google OAuth +│ ├─ modules// business logic per feature (auth, admin, mat-admin, stages, eta, feedback, vehicles) +│ ├─ domain/ pure logic, no I/O: ETA classification, geo maths, date periods, phone numbers +│ ├─ integrations/ Firebase RTDB, Google Maps, Africa's Talking SMS, SMTP +│ ├─ jobs/ ETA cycle, job lease, in-process scheduler +│ ├─ db.ts Prisma client (Neon serverless driver or node-postgres) +│ └─ env.ts validated environment +├─ lib/client/ browser API client (auto token refresh), session, hooks +├─ components/ shared UI (headers, live vehicle table) +└─ styles/pages/ the original page styles, scoped per page +prisma/ schema.prisma, migrations/, seed.ts +tests/ unit/, api/ (route handlers), integration/ (real Postgres) +docs/MIGRATIONS.md migration rules +``` + +Request flow: `route()` applies the rate limit, authenticates the bearer token (checking the user row, +so blocking a user or resetting a password takes effect immediately), awaits params, runs the handler +and maps `HttpError` / Zod / Prisma errors to `{ message, errors? }` JSON. + +**Auth:** short-lived access token (15 min) in the `Authorization` header, httpOnly refresh cookie +(7 days, `SameSite=Lax`, path `/api/auth`) with rotation and reuse detection. Every JWT carries a `typ` +claim so a 2FA temp token can't be used as an access token. Revoked token IDs live in Postgres. + +**ETA cycle:** reads all GPS fixes from Firebase, computes ETAs per stage (Google Directions, only +inside a 5 km geofence), publishes them to Firebase `etas/`, counts a trip once per arrival, +texts subscribers, and accumulates daily distance. A lease row in `job_locks` guarantees only one run +at a time across instances. It runs: + +| Where you deploy | How the cycle runs | Set | +| --- | --- | --- | +| VM / container (`next start`) | in-process every 30 s | nothing (default) | +| Vercel Pro | Vercel Cron → `/api/cron/eta-cycle?runs=2` every minute (two runs, 30 s apart) | `CRON_SECRET`, add the cron below | +| Vercel Hobby / other serverless | an external scheduler calling `/api/cron/eta-cycle`, or `npm run eta:worker` on a small VM | `CRON_SECRET` or `ETA_CYCLE_MODE=off` + worker | + +Vercel Pro `vercel.json` (Hobby only allows daily crons and rejects this at deploy time): + +```json +{ "crons": [{ "path": "/api/cron/eta-cycle?runs=2", "schedule": "* * * * *" }] } +``` + +## Getting started + +Prerequisites: Node.js ≥ 22 and a Neon project (or any PostgreSQL 15+). + +```bash +git clone https://github.com/cliffkoome/MatConnect && cd MatConnect +npm install # also runs `prisma generate` +cp .env.example .env # read by Next.js, the Prisma CLI and scripts +# fill in DATABASE_URL (pooled), DIRECT_URL (direct) and the two JWT secrets +npm run db:migrate:deploy # create the schema +npm run db:seed # optional demo data (password: matconnect123) +npm run dev # http://localhost:3000 +``` + +The first account to register becomes the Admin. Optional integrations (Google sign-in, Maps, Firebase, +SMS, email) switch on when their variables are set; see `.env.example`. + +## Scripts + +| Script | What it does | +| --- | --- | +| `npm run dev` / `build` / `start` | Next.js | +| `npm run lint` / `typecheck` | ESLint, `tsc --noEmit` | +| `npm test` | unit + route tests (no database needed) | +| `npm run test:integration` | tests against a real, migrated Postgres (`DATABASE_URL`) | +| `npm run db:migrate:create -- --name x` | generate a migration from schema changes, without applying it | +| `npm run db:migrate:dev` | apply pending migrations to your dev database | +| `npm run db:migrate:deploy` | apply migrations (CI / production) | +| `npm run db:migrate:status` | show applied / pending migrations | +| `npm run db:migrate:check` | enforce the migration rules | +| `npm run db:seed` / `db:studio` | seed demo data / open Prisma Studio | +| `npm run eta:worker` | run the ETA cycle as a standalone process | + +## Database & migrations + +Read **[docs/MIGRATIONS.md](docs/MIGRATIONS.md)** before changing `prisma/schema.prisma`. In short: +generate migrations with `db:migrate:create`, review the SQL, never edit a merged migration, keep +changes backward compatible, mark destructive SQL explicitly, and let CI deploy to production. + +CI (`.github/workflows/`): +- **ci.yml** — lint, typecheck, tests, build; then migration rules, drift check, apply history to a + fresh Postgres, verify DB == schema, integration tests, seed. +- **neon-preview.yml** — per-PR Neon branch copied from production with the PR's migrations applied. +- **deploy-migrations.yml** — `prisma migrate deploy` to production on merge to `main` (only path to prod). + +## API + +Same endpoints as the Express version (`api-tests.http` has a request for each): +`/api/auth/*` (register, login, refresh, logout, me, profile, password reset, 2FA, Google), +`/api/stages`, `/api/eta/:stageId`, `/api/feedback`, `/api/admin/*`, `/api/mat-admin/*`, +plus `/api/health` and `/api/cron/eta-cycle`. Old `*.html` page URLs redirect to the new routes. diff --git a/api-tests.http b/api-tests.http index 5d69761..35fcdcf 100644 --- a/api-tests.http +++ b/api-tests.http @@ -10,7 +10,7 @@ ### ─── Variables ──────────────────────────────────────────── -@baseUrl = http://localhost:5000/api +@baseUrl = http://localhost:3000/api @adminToken = PASTE_ADMIN_ACCESS_TOKEN_HERE @matAdminToken = PASTE_MAT_ADMIN_ACCESS_TOKEN_HERE @passengerToken = PASTE_PASSENGER_ACCESS_TOKEN_HERE @@ -51,6 +51,13 @@ Content-Type: application/json } +### + +### 2b. Get current profile (any role) — preferred over the role-specific routes below +GET {{baseUrl}}/auth/me +Authorization: Bearer {{passengerToken}} + + ### ### 3. Get current Admin profile @@ -547,3 +554,17 @@ Authorization: Token invalid_format_here ### 54. Expired / tampered token — expect 401 GET {{baseUrl}}/stages Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.tampered.signature + + +### + +### 55. Health check (database reachability) — no auth +GET {{baseUrl}}/health + + +### + +### 56. Run one ETA cycle (what Vercel Cron / a scheduler calls) — needs CRON_SECRET +@cronSecret = PASTE_CRON_SECRET_HERE +GET {{baseUrl}}/cron/eta-cycle +Authorization: Bearer {{cronSecret}} diff --git a/docs/MIGRATIONS.md b/docs/MIGRATIONS.md new file mode 100644 index 0000000..525ff82 --- /dev/null +++ b/docs/MIGRATIONS.md @@ -0,0 +1,157 @@ +# Database migrations (Prisma + Neon) + +`prisma/schema.prisma` is the source of truth for the database. Every change to it ships as a +committed, reviewed SQL migration in `prisma/migrations/`, and only CI applies migrations to +production. The rules below are enforced automatically where possible (see [Enforcement](#enforcement)). + +## Connection strings + +| Variable | Used by | Neon value | +| --- | --- | --- | +| `DATABASE_URL` | the app at runtime | **pooled** URL (host contains `-pooler`) | +| `DIRECT_URL` | Prisma CLI (`migrate`, `db`, `studio`, seed) | **direct** URL (no `-pooler`) | +| `SHADOW_DATABASE_URL` | `migrate dev` / drift checks, optional | a throwaway database or Neon branch | + +Migrations need a direct session (advisory locks, DDL in one connection), which PgBouncer's +transaction pooling doesn't provide — so `prisma.config.ts` always gives the CLI `DIRECT_URL` when set. + +## Everyday workflow + +1. Work against **your own database**: a Neon branch (`dev/`, branched from `main`) or a + local Postgres. Never point `migrate dev` at a shared or production database. +2. Edit `prisma/schema.prisma`. +3. Generate the migration without applying it, so you can read it first: + ```bash + npm run db:migrate:create -- --name add_vehicle_colour + ``` +4. Review `prisma/migrations/_add_vehicle_colour/migration.sql`. Add anything Prisma can't + express (CHECK constraints, data backfills, partial indexes) by hand, with a comment saying why. +5. Apply it locally and regenerate the client: `npm run db:migrate:dev`. +6. Run `npm run db:migrate:check` and the tests, then commit the schema **and** the migration together. + +When you open the PR, CI applies the full migration history to a fresh Postgres, checks for drift and, +if Neon is configured, applies your new migrations to a copy of production data (see below). + +## Rules + +### R1 — Merged migrations are immutable +Never edit, rename or delete a migration that is on `main`. Prisma stores a checksum of every applied +migration; changing one makes `migrate deploy` fail or, worse, leaves environments silently different. +Fix mistakes with a **new** migration. (Editing a migration that only exists on your branch is fine.) + +### R2 — Migrations apply in order +Folder names are `YYYYMMDDHHMMSS_snake_case_description` (UTC). A new migration must sort after +the latest one on `main`; if `main` moved on while your PR was open, rebase and regenerate yours +(delete your unmerged folder, `npm run db:migrate:create` again). + +### R3 — Only CI deploys to production +Production changes only through `prisma migrate deploy` in the **Deploy migrations** workflow after a +merge to `main`. No `prisma db push`, `migrate dev`, `migrate reset` or hand-written DDL against +production — ever. `db push` is fine for throwaway experiments on your own branch only. + +### R4 — Every migration must be safe for the version that's already running +Migrations and app deploys aren't atomic, so the code that is running must work with both the old and +the new schema. Ship schema additions **before** (or in the same release as, gated by the deploy +workflow) the code that needs them, and use **expand → migrate → contract** for anything that removes +or renames: + +| Change | Release 1 (expand) | Release 2 | Release 3 (contract) | +| --- | --- | --- | --- | +| Rename column | add new column, write both, backfill | read new column | drop old column | +| Drop column | stop using it in code | — | drop it | +| Make column required | add as nullable + backfill | code always writes it | `SET NOT NULL` | +| Change type | add new column + backfill | switch reads | drop old column | + +### R5 — Destructive SQL needs an explicit, reviewed opt-in +`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, unbounded `DELETE`, `ALTER COLUMN … TYPE`, `SET NOT NULL`, +renames and `DROP TYPE` fail the check unless the migration contains: + +```sql +-- migration-safety: destructive-approved +``` + +The reviewer approving the PR is confirming that reason. Take a Neon branch snapshot of production +before merging (Neon console → Branches → Create branch from `main`) so you can restore instantly. + +### R6 — One concern per migration, and keep it small +Schema change and big data backfill go in separate migrations. Large backfills should be batched or +run as a script, not a single `UPDATE` that locks the table. + +### R7 — Indexes on big tables +`CREATE INDEX` blocks writes while it builds. For large tables use `CREATE INDEX CONCURRENTLY`, and +make it the **only statement** in its migration (Postgres can't run it inside the implicit transaction +of a multi-statement script). The checker warns on non-concurrent index creation in new migrations. + +### R8 — Schema conventions +- Tables snake_case plural (`@@map`), columns snake_case (`@map`), models PascalCase singular. +- Timestamps are `timestamptz` (`@db.Timestamptz(3)`), calendar days are `date` (`@db.Date`). The + app sets its sessions to UTC; don't depend on the server's `TimeZone`. +- Every foreign key states its `onDelete`. +- Constraints Prisma can't model (CHECKs, partial indexes) live in the migration SQL with a comment, + and are mentioned in a `///` doc comment on the field. +- No semicolons inside SQL comments (some migration runners split statements on `;`). + +### R9 — No drift +`schema.prisma` must equal the result of applying every migration. Don't change one without the other. + +## Enforcement + +| Check | Where | Command | +| --- | --- | --- | +| Folder names, non-empty SQL, lock file | local + CI | `npm run db:migrate:check` | +| R1 immutability, R2 ordering, R5 destructive guard | CI (against `origin/main`) | `MIGRATIONS_BASE_REF=origin/main npm run db:migrate:check` | +| R9 drift: migrations vs schema | CI | `prisma migrate diff --from-migrations prisma/migrations --to-schema prisma/schema.prisma --exit-code` | +| History applies cleanly to an empty DB, then DB == schema | CI (Postgres service) | `prisma migrate deploy` + `prisma migrate diff --from-config-datasource --to-schema … --exit-code` | +| New migrations apply to a copy of production | CI (Neon preview branch) | `.github/workflows/neon-preview.yml` | +| R3 production deploys | CI on `main` only | `.github/workflows/deploy-migrations.yml` | + +Recommended GitHub settings: protect `main`, require the **CI** checks, and add `prisma/migrations/**` +to `CODEOWNERS` so a maintainer reviews every migration. + +## Neon setup + +1. Create a Neon project; the default branch (`main`) is production. +2. Repository **secrets**: `DATABASE_URL_DIRECT_PRODUCTION` (direct URL of `main`) and `NEON_API_KEY`. + Repository **variable**: `NEON_PROJECT_ID`. +3. Create a GitHub **environment** named `production` (optionally with required reviewers) — the + deploy workflow runs in it, so you can gate production migrations on an approval. +4. In your hosting provider set `DATABASE_URL` (pooled) for the app. The build only needs + `prisma generate`, not a database. + +With `NEON_PROJECT_ID` set, each PR that touches `prisma/` gets a fresh `preview/pr-` branch +copied from production; CI applies the PR's migrations to it and checks the result matches the schema, +so a migration that would fail on real data fails the PR instead of the deploy. The branch is deleted +when the PR closes. (Neon branches are copy-on-write, so this costs seconds, not a full copy.) + +## When a production migration fails + +`migrate deploy` stops at the failing migration and records it as failed; nothing after it runs. + +1. Look at the error in the workflow log. Neon makes it cheap to inspect: branch from `main` and + reproduce. +2. If the migration **did not** change anything (e.g. failed on the first statement): + `npx prisma migrate resolve --rolled-back `, fix with a *new* migration, redeploy. +3. If it **partially** applied: finish or undo the remaining changes by hand on a Neon branch first, + then `npx prisma migrate resolve --applied ` (finished) or `--rolled-back` (undone). + `prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --script` shows what + is still missing. +4. Last resort: restore production from a Neon branch / point-in-time restore. + +## Moving data from the old MariaDB database + +The Express app let Sequelize `sync()` its tables, so there's no migration history to carry over. The +init migration creates the Postgres schema from scratch; copy data across once, before go-live: + +| MariaDB (Sequelize) | Postgres | Notes | +| --- | --- | --- | +| `Users` | `users` | camelCase → snake_case columns; `role` → enum `user_role`; bcrypt hashes and 2FA secrets carry over unchanged | +| `Stages` | `stages` | | +| `Vehicles` | `vehicles` | `deletedAt` keeps soft-deleted rows | +| `StageVehicles` (`StageId`, `VehicleId`) | `stage_vehicles` (`stage_id`, `vehicle_id`) | | +| `UserStageSubscriptions` (`UserId`, `StageId`) | `stage_subscriptions` | | +| `DailyDistances`, `DailyTrips`, `Feedbacks` | `daily_distances`, `daily_trips`, `feedback` | | + +[pgloader](https://pgloader.io/) can do MySQL → Postgres in one pass with a column-rename map; or export +CSVs and `\copy` them in. Afterwards reset the sequences, e.g. +`SELECT setval(pg_get_serial_sequence('users','id'), (SELECT max(id) FROM users));`, for every table +with a serial `id`. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1647f2e --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,24 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +export default defineConfig([ + ...nextVitals, + ...nextTs, + globalIgnores([".next/**", "node_modules/**", "src/generated/**", "coverage/**", "next-env.d.ts"]), + { + rules: { + "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + }, + }, + { + // Server code must not be imported from client components and vice versa. + files: ["src/app/**/*.tsx", "src/components/**/*.tsx", "src/lib/client/**/*.ts"], + rules: { + "no-restricted-imports": [ + "error", + { patterns: [{ group: ["@/server/*", "@/generated/*"], message: "Client code must not import server modules." }] }, + ], + }, + }, +]); diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..5819eda --- /dev/null +++ b/next.config.ts @@ -0,0 +1,57 @@ +import type { NextConfig } from "next"; + +/** Old static pages → new App Router routes, so existing bookmarks and printed QR links keep working. */ +const legacyPages: Record = { + "/index.html": "/", + "/login.html": "/login", + "/signup.html": "/signup", + "/forgot-password.html": "/forgot-password", + "/auth-success.html": "/auth/success", + "/select-stage.html": "/stages", + "/feedback-page.html": "/feedback", + "/user-profile.html": "/profile", + "/admin-dashboard.html": "/admin", + "/manage-stages.html": "/admin/stages", + "/manage-users.html": "/admin/users", + "/mat-admin-dashboard.html": "/mat-admin", + "/mat-admin-vehicles.html": "/mat-admin/vehicles", +}; + +const securityHeaders = [ + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "X-Frame-Options", value: "DENY" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" }, + { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" }, +]; + +const nextConfig: NextConfig = { + reactStrictMode: true, + poweredByHeader: false, + // Server-only SDKs that should be required at runtime rather than bundled. + serverExternalPackages: ["firebase-admin", "pg", "nodemailer"], + images: { + remotePatterns: [{ protocol: "https", hostname: "lh3.googleusercontent.com" }], + }, + async redirects() { + return [ + { + source: "/stage-info.html", + has: [{ type: "query", key: "stageId", value: "(?\\d+)" }], + destination: "/stages/:stageId", + permanent: true, + }, + { source: "/stage-info.html", destination: "/stages", permanent: true }, + ...Object.entries(legacyPages).map(([source, destination]) => ({ + source, + destination, + permanent: true, + })), + ]; + }, + async headers() { + return [{ source: "/:path*", headers: securityHeaders }]; + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 94e809f..63ae954 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1997 +1,7347 @@ { - "name": "grp-a-isp-cliffkoome", - "version": "1.0.0", + "name": "matconnect", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "grp-a-isp-cliffkoome", - "version": "1.0.0", + "name": "matconnect", + "version": "2.0.0", + "hasInstallScript": true, "license": "ISC", "dependencies": { - "africastalking": "^0.7.7", - "axios": ">=1.18.0", - "bcrypt": "^6.0.0", - "body-parser": ">=2.3.0", - "brace-expansion": ">=5.0.9", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^17.2.2", - "express": "^5.1.0", - "fast-xml-parser": ">=5.7.0", - "firebase-admin": "^13.5.0", - "form-data": ">=4.0.6", - "joi": ">=18.2.1", - "jsonwebtoken": "^9.0.2", - "lodash": ">=4.18.0", - "mariadb": ">=3.4.6", - "node-forge": ">=1.4.0", - "nodemailer": ">=9.0.1", - "passport": "^0.7.0", - "passport-google-oauth20": "^2.0.0", - "path": "^0.12.7", - "protobufjs": ">=7.6.5", + "@neondatabase/serverless": "^1.2.0", + "@prisma/adapter-neon": "^7.10.1", + "@prisma/adapter-pg": "^7.10.1", + "@prisma/client": "^7.10.1", + "bcryptjs": "^3.0.3", + "chart.js": "^4.5.1", + "firebase-admin": "^14.5.0", + "jose": "^6.2.12", + "next": "^16.4.2", + "nodemailer": "^10.1.7", + "otplib": "^13.5.0", + "pg": "^8.23.1", "qrcode": "^1.5.4", - "rate-limit-redis": "^4.2.0", - "sequelize": "^6.37.7", - "speakeasy": "^2.0.0", - "uuid": ">=11.1.1", - "websocket-driver": ">=0.7.5" + "react": "^19.3.1", + "react-chartjs-2": "^5.3.1", + "react-dom": "^19.3.1", + "server-only": "^0.0.1", + "zod": "^4.6.7" }, "devDependencies": { - "axios": ">=1.18.0", - "body-parser": ">=2.3.0", - "brace-expansion": ">=5.0.9", - "fast-xml-parser": ">=5.7.0", - "form-data": ">=4.0.6", - "joi": ">=18.2.1", - "lodash": ">=4.18.0", - "node-forge": ">=1.4.0", - "nodemailer": ">=9.0.1", - "nodemon": "^3.1.10", - "protobufjs": ">=7.6.5", - "uuid": ">=11.1.1", - "websocket-driver": ">=0.7.5" - } - }, - "node_modules/@fastify/busboy": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", - "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", - "license": "MIT" - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.4.tgz", - "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-types": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz", - "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==", - "license": "Apache-2.0", + "@types/node": "^22.20.5", + "@types/nodemailer": "^8.0.2", + "@types/pg": "^8.23.1", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "dotenv": "^18.0.8", + "eslint": "^9.39.5", + "eslint-config-next": "^16.4.2", + "prisma": "^7.10.1", + "tsx": "^4.23.15", + "typescript": "^6.0.3", + "vitest": "^5.0.5", + "vitest-mock-extended": "^5.1.1" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/logger": "0.5.1" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.5.tgz", - "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==", - "license": "Apache-2.0" + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@firebase/component": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz", - "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==", - "license": "Apache-2.0", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@firebase/database": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz", - "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==", - "license": "Apache-2.0", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/app-check-interop-types": "0.3.4", - "@firebase/auth-interop-types": "0.2.5", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@firebase/database-compat": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz", - "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==", - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/database": "1.1.3", - "@firebase/database-types": "1.0.20", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@firebase/database-types": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz", - "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==", - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "@firebase/app-types": "0.9.5", - "@firebase/util": "1.15.1" + "yallist": "^3.0.2" } }, - "node_modules/@firebase/logger": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.1.tgz", - "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@firebase/util": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz", - "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/firestore": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz", - "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.3.0", - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^4.3.3", - "protobufjs": "^7.2.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/firestore/node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "optional": true, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=12.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@google-cloud/paginator": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", - "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/projectify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", - "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/promisify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", - "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/storage": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", - "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "dev": true, + "license": "MIT", "dependencies": { - "@google-cloud/paginator": "^5.0.0", - "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "<4.1.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "duplexify": "^4.1.3", - "fast-xml-parser": "^5.3.4", - "gaxios": "^6.0.2", - "google-auth-library": "^9.6.3", - "html-entities": "^2.5.2", - "mime": "^3.0.0", - "p-limit": "^3.0.1", - "retry-request": "^7.0.0", - "teeny-request": "^9.0.0", - "uuid": "^8.0.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/storage/node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=14" + "node": ">=6.0.0" } }, - "node_modules/@google-cloud/storage/node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/storage/node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/storage/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "yocto-queue": "^0.1.0" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/storage/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } + "node_modules/@electric-sql/pglite": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.3.tgz", + "integrity": "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==", + "devOptional": true, + "license": "Apache-2.0" }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", - "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "node_modules/@electric-sql/pglite-socket": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.3.tgz", + "integrity": "sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" + "bin": { + "pglite-server": "dist/scripts/server.js" }, - "engines": { - "node": ">=12.10.0" + "peerDependencies": { + "@electric-sql/pglite": "0.4.3" } }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", - "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "node_modules/@electric-sql/pglite-tools": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.3.tgz", + "integrity": "sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==", + "devOptional": true, "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.4.3" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@grpc/grpc-js/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", "optional": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" + "tslib": "^2.4.0" } }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@grpc/grpc-js/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "os": [ + "aix" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@grpc/grpc-js/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@grpc/grpc-js/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, + "os": [ + "android" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@grpc/grpc-js/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@hapi/address": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", - "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@hapi/formula": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", - "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/hoek": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/pinpoint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", - "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", - "license": "BSD-3-Clause" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@hapi/tlds": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", - "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", - "license": "BSD-3-Clause", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, "license": "MIT", "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" ], - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "license": "Apache-2.0", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.3.tgz", + "integrity": "sha512-fmo/IKgkenPzz3qTKG+sIUXdF/Kv83AkvvOXJHPTHJeKJbat8SsL1ihUaFaDotqoEl4UZ1Lf8Eb/3vK6eaWX2A==", + "license": "MIT" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.5.tgz", + "integrity": "sha512-qId34pVZ2CXTmtu4ofW5leiI93DvnOvIcr/5GT7MZPw5WXAi6nZoU+g9cNRgl7K90UJSbK0cXxten4meYMPyZg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.6.tgz", + "integrity": "sha512-yPLahy7Esfu2w/yme3msVK4xTkDXQqq6szfQn8yVOQpCKiT5GVFjqNpLbuz6NkX0WuTwUidkmPewW3r4xkvpeg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/logger": "0.5.2" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.6.tgz", + "integrity": "sha512-FgwZqDrBqgK0BHI70QTv1v/5wmuDF9f8fsJFvKSxoRlK2PPfSQtZAk2jhXu4pe9BZzFi/e4UYusU/bEQHdf6Qg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.5.tgz", + "integrity": "sha512-vuFDcL91Q+2ZuBJkyOh86T4q0B4ffNTDjc/A38tybO56odQABxRTFLTIowCWqAKeIcgo37GowWMVgFF73gD8Qw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.5.tgz", + "integrity": "sha512-/JGpvszLoNXNgzilRXocigGfFF4hbcPA9wN1i1kjJx6oKkXgkHteZYl3lQs1lJX3ETDf6bv7zI15lPMVUp4sAQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.5", + "@firebase/auth-interop-types": "0.2.6", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.7.tgz", + "integrity": "sha512-lBq9sJm8MnJINKJnkAKSOj2MbC66xGoSVCcGkPtstl+lkoKEBtD46qHLbuDgW/WbLPoKVm17RIN8BxHj+Z2F2w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/database": "1.1.5", + "@firebase/database-types": "1.0.22", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + }, + "peerDependenciesMeta": { + "@firebase/app": { + "optional": true + }, + "@firebase/app-compat": { + "optional": true + } + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.22.tgz", + "integrity": "sha512-YAZNXsjY9EQQ+pKw/3ax8n5FgolHC7Qew7EY5RceYdl0R2ZP+kCv1O0DkKlcceue8uQCOreHCNN7PWVFpY1Nug==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.6", + "@firebase/util": "1.15.3" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.2.tgz", + "integrity": "sha512-J2VO4NFTc0xQFrxV1B/lm5balicm9cwuX2acR9Yn41fN8KgUeQFo+VJV222IqW2FPSXKDu9uo5WdQFWf9TPbYg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.3.tgz", + "integrity": "sha512-c/z/gaIlaaLZEuGbE6sLUuJ61tskg1JghvhcNQzW948ASBinbVBBRnZTC4b4yt4LaEtJQkYlyzqLHcutFwEIvA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-9.3.1.tgz", + "integrity": "sha512-ofGTiSpfXrHrrS3KaDOmsupSzCHr4WVl1+s6Ke0F8rPBRyb/OuZSC8u1NjCKtt/fO7A1GvqASQGhdAwVsnGpQA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/firestore-api": "^0.2.0", + "@opentelemetry/api": "^1.9.0", + "fast-deep-equal": "^3.1.3", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^6.0.1", + "protobufjs": "^7.5.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@google-cloud/firestore-api": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore-api/-/firestore-api-0.2.0.tgz", + "integrity": "sha512-wfHJPZmLqzpy6FN6U+7HkkP/AKhGsbHSpEy/qL2TyI3/pKRAvpamQxZ2qvps5hMRgAjfuHCim+oeJj9RveCaqg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "google-gax": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gcp-metadata": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.4.tgz", + "integrity": "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-gax": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.8.tgz", + "integrity": "sha512-M4vpZcXQIC1gqIVGQ7eaU3jXQA6zecStyTXu514TYfThlgSurYJOxHZo9fzU6hAgwPWvuEynAVHWyaIk80VeEA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", + "node-fetch": "^3.3.2", + "object-hash": "^3.0.0", + "proto3-json-serializer": "3.0.4", + "protobufjs": "^7.5.4", + "retry-request": "^8.0.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/proto3-json-serializer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/retry-request": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.4.tgz", + "integrity": "sha512-pI6/7eabUYkZxamkOq0g0uMxKLLGnjzhefY+vL8bVXag5rto4OU2YBTPytWLuHH7aEKD6fn7kJycQfid0Mwnkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "teeny-request": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/teeny-request": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.4.tgz", + "integrity": "sha512-R1Cg4Vu0UULeDfHL/kjABLaTW++9yD/B6n2g48y5dJ04hsEaxcfmAqbNDzNsbqAYJyIpZafjklLG9YxRu9uzOg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-7.2.0.tgz", + "integrity": "sha512-l+x6YObHGTuqBCMvC0SXo3Cnyqg4FkWWKYRptf46PACHJ5GAUuhsd2b3wphgGrg1ybs/LAb+q5MnUTuG/3+v7w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-6.2.0.tgz", + "integrity": "sha512-QXBPu/D+1zgJ9Ydw+GXYws9OipW62nxfwsGl7x3JUTDim9NCGb7R+h7Sj3HKo1WMYb/StvOYyUBZrqU2pkhuiw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=22" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-6.2.0.tgz", + "integrity": "sha512-0i3JkkwAdGGk6AlMnFm1Vf+XtpIL73ki/MZWvXDPoN37nABf/9LFHtYeo8tW77vHSQS00ykC2ygHnUL1gkYAXg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=22" + } + }, + "node_modules/@google-cloud/storage": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-8.4.0.tgz", + "integrity": "sha512-M3kA6UA2Tjp5aznCrgKcm9CzOjiQyRMnquqalqXAeS5wvkqqZxN8wpNjjHafOSYs6lQ82U7f2i8we4eTfIrpkA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^7.0.1", + "@google-cloud/projectify": "^6.0.1", + "@google-cloud/promisify": "^6.0.1", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^5.3.4", + "gaxios": "^7.1.4", + "google-auth-library": "^11.0.0", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^9.0.1", + "teeny-request": "^11.0.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.6.tgz", + "integrity": "sha512-SgC/B3/VZ8lTyOZ4Cwk6f1skWhmkNAaVfiX90qd5u1rRXGKk1//8FTUEN9GvOUq7rfWIqAYkA3CoyumYVpOS6g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT", + "optional": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@neondatabase/serverless": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.2.0.tgz", + "integrity": "sha512-tnaqgixUengZQNd9ZrS3ytQfamJzLKY8QxgqWQTNHHtB+KrFQWk77JTbbpg4RD+J3qE4eO2lVytvGDX5Kj6FdQ==", + "license": "MIT", + "engines": { + "node": ">=19.0.0" + } + }, + "node_modules/@next/env": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.2.tgz", + "integrity": "sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.4.2.tgz", + "integrity": "sha512-gCTWlw0e+Y6kajpFyKKgA195jUJCRhlzJE0lV7ufe8a+ghIsANWZ0/FiJwg6DuOG1+OvRbcZDcFBoRh3mNJx0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.2.tgz", + "integrity": "sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.2.tgz", + "integrity": "sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.2.tgz", + "integrity": "sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.2.tgz", + "integrity": "sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.2.tgz", + "integrity": "sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.2.tgz", + "integrity": "sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.2.tgz", + "integrity": "sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.2.tgz", + "integrity": "sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodable/entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", + "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@otplib/core": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.5.0.tgz", + "integrity": "sha512-2rURdkYkb3BDhMs3j/oCCPTve1ybJ6ruLfLfSe1ZSPV+y6RFTbLfAuT0m0ZCnps88ogkIq9t/+Li/kg5RofQwQ==", + "license": "MIT" + }, + "node_modules/@otplib/hotp": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.5.0.tgz", + "integrity": "sha512-1EwwAti05CeWJn4xXOVMBK9N6dIJlJw/cQkQyiL9OVlkeS452wAve3ffodi+5IUZdWoF1wxVug38RisdxDqDWw==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.5.0", + "@otplib/uri": "13.5.0" + } + }, + "node_modules/@otplib/plugin-base32-scure": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.5.0.tgz", + "integrity": "sha512-3JEIHindUMiIeNL0jXepSAkZ/7IkWq4sPdG9eXK0lrMXZG9RdKu5oz/4tuuTodtsFUPYhKlRMqv5xUvQIkMIaA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.5.0", + "@scure/base": "^2.2.0" + } + }, + "node_modules/@otplib/plugin-crypto-noble": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.5.0.tgz", + "integrity": "sha512-fihOAGFvc4b8XTKyIK3jifFP2nLrUNc2bOaJ3UkUKJjy1XI2FbHKG0WmOH+jFsPfa6VsOnouCnEmSznDxIe0pA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@otplib/core": "13.5.0" + } + }, + "node_modules/@otplib/totp": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.5.0.tgz", + "integrity": "sha512-GD9LzQnbHDwXCp79s8AaiA5cJR6luX5fEr9AN32AWx4ooRvllPbMkG80gHSA0jbGCnAIX/ChyOtTZ8tgJHMkSg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.5.0", + "@otplib/hotp": "13.5.0", + "@otplib/uri": "13.5.0" + } + }, + "node_modules/@otplib/uri": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.5.0.tgz", + "integrity": "sha512-LsL1hqTEgJHY40U2eb/Qp6OR1tKkNGwHQTrqqjIuJj0cawGWkLEmeUFK1MHzWBQ4bgy5IdeB5Et7gIDSAH/CRw==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.5.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/adapter-neon": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-7.10.1.tgz", + "integrity": "sha512-qqOFao+72wNBA8q5pX/1/9sn+h7vj8/Qy6weMqPk7F2eALJqevYwvjkIi5RMgxAYcNJwkGES+pXbLMqPNPeH1Q==", + "license": "Apache-2.0", + "dependencies": { + "@neondatabase/serverless": ">0.6.0 <2", + "@prisma/driver-adapter-utils": "7.10.1", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/adapter-pg": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.10.1.tgz", + "integrity": "sha512-EP9ruPYHjKP80WZlb5qVaEXKgZjJieBFjAxcBtQnDAHD6+16UN4keVmBh6L/RUpuDQT2FQRbvKNgq5d4Ix+IkQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.10.1", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/client": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.10.1.tgz", + "integrity": "sha512-5tgAbFGh//zVOC6WPNVgziOErJHYkUE+wmqCwbIyq6IrTXUYtv9y2iBFGh/Owo99Mx0+cr0j1FOrxkYBMuyhaQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.10.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.1.tgz", + "integrity": "sha512-AzoATVIgmO3lcZfM443+9SI7+cyLMW2+Psw4op8WQHpHvHCX//CKxv8fwhiL0yGWF8DjdQrFewxrZo5McBdB8g==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.1.tgz", + "integrity": "sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.3.4", + "deepmerge-ts": "8.0.2", + "effect": "3.20.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz", + "integrity": "sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.24.17", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.17.tgz", + "integrity": "sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.4.3", + "@electric-sql/pglite-socket": "0.1.3", + "@electric-sql/pglite-tools": "0.3.3", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.11", + "find-my-way": "9.7.0", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.4.2", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.1.tgz", + "integrity": "sha512-DJ4c6xvVnm3s9ZUyql9nCUicADSqyvH+CjXRCM9L6m5zJxCNmebCElG6X4irICq3rosBwlX+S19Dxq/4IrZk3A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1" + } + }, + "node_modules/@prisma/engines": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.1.tgz", + "integrity": "sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/fetch-engine": "7.10.1", + "@prisma/get-platform": "7.10.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz", + "integrity": "sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz", + "integrity": "sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/get-platform": "7.10.1" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/streams-local": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.11.tgz", + "integrity": "sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.2.0", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/streams-local/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@prisma/streams-local/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@prisma/studio-core": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.33.0.tgz", + "integrity": "sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "@visx/curve": "4.0.1-alpha.0", + "@visx/event": "4.0.1-alpha.0", + "@visx/grid": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/responsive": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/shape": "4.0.1-alpha.0", + "d3-array": "3.2.4", + "d3-shape": "3.2.0", + "elkjs": "0.11.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause", - "optional": true + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.4.0.tgz", + "integrity": "sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", + "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.26", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.26.tgz", + "integrity": "sha512-48NWfiY6oNcKOh47Z4h+sIvwcQR91h6KPhaYKgeMIrkT2jV82Gy90SErj60vW8jQbeMSOSbhIv3bZ7JoqPPVhA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.2.tgz", + "integrity": "sha512-c7M5ox8p0nEOfbJ2E9Qcmt8/QCu/VzsiAhzBiZbvky2PhKZDHpKB3sQHBM5MEZNkCfBOaS6S9//AGPRTNB7IaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz", + "integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz", + "integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz", + "integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@visx/curve": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-4.0.1-alpha.0.tgz", + "integrity": "sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@visx/vendor": "4.0.0-alpha.0" + } + }, + "node_modules/@visx/event": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/event/-/event-4.0.1-alpha.0.tgz", + "integrity": "sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@visx/point": "4.0.1-alpha.0" + } + }, + "node_modules/@visx/grid": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-4.0.1-alpha.0.tgz", + "integrity": "sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@visx/curve": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/point": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/shape": "4.0.1-alpha.0", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/group": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-4.0.1-alpha.0.tgz", + "integrity": "sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/point": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-4.0.1-alpha.0.tgz", + "integrity": "sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@visx/responsive": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-4.0.1-alpha.0.tgz", + "integrity": "sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.17.13", + "@types/react": "*", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/scale": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-4.0.1-alpha.0.tgz", + "integrity": "sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@visx/vendor": "4.0.0-alpha.0" + } + }, + "node_modules/@visx/shape": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-4.0.1-alpha.0.tgz", + "integrity": "sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.17.13", + "@types/react": "*", + "@visx/curve": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/vendor": "4.0.0-alpha.0", + "classnames": "^2.3.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/vendor": { + "version": "4.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", + "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", + "devOptional": true, + "license": "MIT and ISC", + "dependencies": { + "@types/d3-array": "3.0.3", + "@types/d3-color": "3.1.0", + "@types/d3-delaunay": "6.0.1", + "@types/d3-format": "3.0.1", + "@types/d3-geo": "3.1.0", + "@types/d3-interpolate": "3.0.1", + "@types/d3-path": "3.1.1", + "@types/d3-scale": "4.0.2", + "@types/d3-shape": "3.1.7", + "@types/d3-time": "3.0.0", + "@types/d3-time-format": "2.1.0", + "d3-array": "3.2.1", + "d3-color": "3.1.0", + "d3-delaunay": "6.0.2", + "d3-format": "3.1.0", + "d3-geo": "3.1.0", + "d3-interpolate": "3.0.1", + "d3-path": "3.1.0", + "d3-scale": "4.0.2", + "d3-shape": "3.2.0", + "d3-time": "3.1.0", + "d3-time-format": "4.1.0", + "internmap": "2.0.3" + } + }, + "node_modules/@visx/vendor/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", + "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.5", + "estree-walker": "^3.0.3", + "magic-string": "^1.4.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9 || ^3.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", + "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axe-core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.14.0.tgz", + "integrity": "sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", + "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/better-result": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz", + "integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.4.tgz", + "integrity": "sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.29", + "caniuse-lite": "^1.0.30001816", + "electron-to-chromium": "^1.5.459", + "node-releases": "^2.0.59", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001816", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge-ts": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", + "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", + "devOptional": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/deepmerge-ts" + } + ], + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@protobufjs/codegen": { + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/destr": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause", - "optional": true + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "18.0.8", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.8.tgz", + "integrity": "sha512-2KDeoFoK/zRtcnbunCDlLG5VWVMcyqeQc6iK65o231nfNAcuGrKoqUQfJFUtBI+QlShk5HKIHxwebBtUl3rEfA==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "dotenv": "dist/index.cjs" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT", "optional": true }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/effect": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.459", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.459.tgz", + "integrity": "sha512-4SC9HHSFjO/liAhifpdOL5hG9b4BP7/5G3y3UPZVO8trlAXr1CaUEEJL0gWK3kOcYcZrOA5rkwG45Gra/lTIgg==", + "dev": true, + "license": "ISC" + }, + "node_modules/elkjs": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", + "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", + "devOptional": true, + "license": "EPL-2.0" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "optional": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.1" + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@protobufjs/pool": { + "node_modules/es-shim-unscopables": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause", - "optional": true + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } }, - "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { - "node": ">= 10" + "node": ">=6" } }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "node_modules/eslint-config-next": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.4.2.tgz", + "integrity": "sha512-P33vjcETMXc+qBjpF77TeJHZLzk6cPcTy8KjpxLucYB3mCIFIORMcZ+cBXcAiIYjE5RM7jyknEEOwMNdnx/ADQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/ms": "*" + "@next/eslint-plugin-next": "16.4.2", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.1.0", + "globals": "16.4.0", + "typescript-eslint": "^8.56.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" + "node_modules/eslint-config-next/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "node_modules/eslint-config-next/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/ms": "*", - "@types/node": "*" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "node_modules/eslint-config-next/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/ignore": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.13.tgz", + "integrity": "sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">= 4" + } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" + "node_modules/eslint-config-next/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "node_modules/eslint-config-next/node_modules/typescript-eslint": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.2.tgz", + "integrity": "sha512-Rz4effbMy2g9wnvOJZG6SbVavX313Lap1vNEbk1RUochzickvzP8P4HB+bkNg5e50eVz1hQGjh9K1omI1xB4Bw==", + "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.12.0" + "@typescript-eslint/eslint-plugin": "8.71.2", + "@typescript-eslint/parser": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/utils": "8.71.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/request": { - "version": "2.48.13", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", - "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz", + "integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.5" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/type-utils": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz", + "integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==", + "dev": true, "license": "MIT", - "optional": true, "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" + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/request/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz", + "integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==", + "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3" + }, "engines": { - "node": ">= 0.6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/request/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz", + "integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "mime-db": "1.52.0" + "@typescript-eslint/project-service": "8.71.2", + "@typescript-eslint/tsconfig-utils": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz", + "integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.2", + "@typescript-eslint/types": "^8.71.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } }, - "node_modules/@types/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", - "license": "MIT" + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz", + "integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/eslint-config-next/node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz", + "integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "event-target-shim": "^5.0.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2" }, "engines": { - "node": ">=6.5" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" + "ms": "^2.1.1" } }, - "node_modules/africastalking": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/africastalking/-/africastalking-0.7.9.tgz", - "integrity": "sha512-wb0NhvShFoTXVjzpmxjgXGuQkD1DhNqnC6DyEW7SeY/Q7fNN3X2qQjIb0XM8UPgRlx6rZYnvuCf0NW9Vmk33nA==", + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, "license": "MIT", "dependencies": { - "axios": "1.13.5", - "dotenv": "17.2.4", - "google-libphonenumber": "3.2.44", - "joi": "18.0.2", - "lodash": "4.17.23" + "debug": "^3.2.7" }, "engines": { - "node": ">=18" + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/africastalking/node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "ms": "^2.1.1" } }, - "node_modules/africastalking/node_modules/joi": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", - "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", - "license": "BSD-3-Clause", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", "dependencies": { - "@hapi/address": "^5.1.1", - "@hapi/formula": "^3.0.2", - "@hapi/hoek": "^11.0.7", - "@hapi/pinpoint": "^2.0.1", - "@hapi/tlds": "^1.1.1", - "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">= 20" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/africastalking/node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "devOptional": true, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "*" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "retry": "0.13.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, - "node_modules/axios": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", - "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.6", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/base32.js": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz", - "integrity": "sha512-EGHIRiegFa62/SsA1J+Xs2tIzludPdzM064N9wjbiEgHnGnJ1V0WEpA4pEwCYT5nDvZk3ubf0shqaCS7k6xeUQ==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 18" + "node": "*" } }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": "*" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "balanced-match": "^4.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "20 || >=22" + "node": "*" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=0.10" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=6" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=12.0.0" } }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } + "node_modules/exsolve": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", + "devOptional": true, + "license": "MIT" }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "pure-rand": "^6.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "devOptional": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">= 0.8" + "node": ">=8.6.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", + "license": "ISC", "dependencies": { - "safe-buffer": "5.2.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 6" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "devOptional": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "fast-decode-uri-component": "^1.0.1" } }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.6" + "optional": true, + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "node_modules/fast-xml-parser": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", + "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", + "optional": true, "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" + "@nodable/entities": "^3.0.1", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" }, - "engines": { - "node": ">= 0.8.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/cookie-parser/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, "engines": { - "node": ">=6.6.0" + "node": ">=0.8.0" } }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^12.20 || >= 14.13" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, "engines": { - "node": ">= 12" + "node": ">=16.0.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/find-my-way": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz", + "integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==", + "devOptional": true, "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=20" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "node_modules/firebase-admin": { + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-14.5.0.tgz", + "integrity": "sha512-DElmgmaH9aPXu2dWXGQiDoan33UfTnbfAAKaue4fwEZxmpdaNrTk5DmuFn+vVDki2KZ6TiaC0IHRWcEzcDxB3Q==", "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.1.4", + "@firebase/database-types": "^1.0.20", + "fast-deep-equal": "^3.1.1", + "google-auth-library": "^11.1.0", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^4.0.1" + }, + "engines": { + "node": ">=22" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^9.1.0", + "@google-cloud/storage": "^8.1.0" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, "engines": { - "node": ">=0.10" + "node": ">=16" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "license": "MIT" - }, - "node_modules/dotenv": { - "version": "17.2.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", - "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", - "license": "BSD-2-Clause", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/dottie": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.7.tgz", - "integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">=12.20.0" } }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "once": "^1.4.0" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-define-property": { + "node_modules/functional-red-black-tree": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "optional": true }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "es-errors": "^1.3.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", + "node_modules/gcp-metadata": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-9.2.1.tgz", + "integrity": "sha512-5/LvxarRKbGCA5LJ+ha2aV1iVm7/aALIW4PzeXnQR5dyXiNMWbeA4UOBnqERInLfH/xSFp0ZREcxodUzxhEw2Q==", + "license": "Apache-2.0", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "gaxios": "^8.3.0", + "google-logging-utils": "^2.0.0", + "json-bigint": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=22" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "optional": true, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-8.3.1.tgz", + "integrity": "sha512-h4/R7ay7JFXU5kmAkp/Y4Y4Vmm7m83tD579GUgfQQsRRGQ4TzSedyHla6XgTWohQuVffT3ldN4FF6uCqlRGaEA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, "engines": { - "node": ">=6" + "node": ">=22" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">= 16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "devOptional": true, "license": "MIT" }, - "node_modules/farmhash-modern": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", - "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/fast-xml-parser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", - "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.2.0", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.3.0", - "xml-naming": "^0.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { - "fxparser": "src/cli/cli.js" + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", "dependencies": { - "websocket-driver": ">=0.5.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=0.8.0" + "node": ">=10.13.0" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, "engines": { - "node": "^12.20 || >= 14.13" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", + "node_modules/google-auth-library": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.3.1.tgz", + "integrity": "sha512-mQS0R2Ev/DKkzuWSYtDeq15uhDeaWuT2qzLYqJ41LmooxVKHmn5ut3pc6dlHY6wfHW/D7FBmOYjAtkjaD1A7qA==", + "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^8.3.0", + "gcp-metadata": "^9.0.0", + "google-logging-utils": "^2.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=22" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", + "node_modules/google-auth-library/node_modules/gaxios": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-8.3.1.tgz", + "integrity": "sha512-h4/R7ay7JFXU5kmAkp/Y4Y4Vmm7m83tD579GUgfQQsRRGQ4TzSedyHla6XgTWohQuVffT3ldN4FF6uCqlRGaEA==", + "license": "Apache-2.0", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">=8" + "node": ">=22" } }, - "node_modules/firebase-admin": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.10.0.tgz", - "integrity": "sha512-rbuCrJvYRwqBqvbccMS8fj/x2zsaMisdf5RQbRzQzr14Rbq9r2UlpuBHqWAwrO6c9dIRF56xF/xoepXsD5yDuQ==", + "node_modules/google-gax": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-6.12.1.tgz", + "integrity": "sha512-U/OUrt4+aUiELB8sgaPJAdVfPzFOPjo1ngXVOwbR1NcCHV7ZcPOW0Ecwdbp8J86NmxBwLMJUOj8AgRSOZm5iJQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { - "@fastify/busboy": "^3.0.0", - "@firebase/database-compat": "^2.0.0", - "@firebase/database-types": "^1.0.6", - "farmhash-modern": "^1.1.0", - "fast-deep-equal": "^3.1.1", - "google-auth-library": "^10.6.1", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.1.0" + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "@opentelemetry/api": "^1.9.0", + "duplexify": "^4.1.3", + "google-auth-library": "^11.0.0", + "google-logging-utils": "^2.0.0", + "node-fetch": "^3.3.2", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^4.0.0", + "protobufjs": "^7.5.4", + "retry-request": "^9.0.0" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@google-cloud/firestore": "^7.11.0", - "@google-cloud/storage": "^7.19.0" + "node": ">=22" } }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/google-logging-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-2.1.0.tgz", + "integrity": "sha512-1R6uiq6XhXW2ZrcGtZpGgVDsYGzSmveVOn8Pp37qe1fbThHZ34e3t3eXj5mVu6PFSVx9fvB1KNVnYgVtq/BRcA==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/grammex": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.13.tgz", + "integrity": "sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "license": "MIT", + "optional": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "gaxios": "^7.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=18" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, "license": "MIT", "dependencies": { - "fetch-blob": "^3.1.2" + "dunder-proto": "^1.0.0" }, "engines": { - "node": ">=12.20.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "hermes-estree": "0.25.1" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", - "optional": true - }, - "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", "optional": true, "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gaxios/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "optional": true, "engines": { "node": ">= 14" } }, - "node_modules/gaxios/node_modules/https-proxy-agent": { + "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", - "optional": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -2000,114 +7350,102 @@ "node": ">= 14" } }, - "node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "devOptional": true, "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "license": "Apache-2.0", "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=18" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/gcp-metadata/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">= 4" } }, - "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "license": "Apache-2.0", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gcp-metadata/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, "engines": { - "node": ">= 14" + "node": ">=0.8.19" } }, - "node_modules/gcp-metadata/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, "license": "MIT", "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "node": ">= 0.4" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "devOptional": true, "license": "ISC", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=12" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -2116,232 +7454,197 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-auth-library/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/google-auth-library/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "license": "Apache-2.0", "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-auth-library/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "semver": "^7.7.1" } }, - "node_modules/google-auth-library/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-gax": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", - "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", - "license": "Apache-2.0", - "optional": true, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" + "hasown": "^2.0.4" }, "engines": { - "node": ">=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "license": "Apache-2.0", - "optional": true, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", - "license": "Apache-2.0", - "optional": true, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=14" - } - }, - "node_modules/google-gax/node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-gax/node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "optional": true, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" + "call-bound": "^1.0.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-gax/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/google-libphonenumber": { - "version": "3.2.44", - "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.44.tgz", - "integrity": "sha512-9p2TghluF2LTChFMLWsDRD5N78SZDsILdUk4gyqYxBXluCyxoPiOq+Fqt7DKM+LUd33+OgRkdrc+cPR93AypCQ==", - "license": "(MIT AND Apache-2.0)", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { "node": ">= 0.4" }, @@ -2349,34 +7652,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2385,13 +7691,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2400,237 +7718,279 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "call-bound": "^1.0.3" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "devOptional": true, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inflection": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", - "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", - "engines": [ - "node >= 0.4.0" + "node_modules/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } ], - "license": "MIT" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "license": "MIT", + "optional": true }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/joi": { - "version": "18.2.8", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.8.tgz", - "integrity": "sha512-G2TX62h58ZHuwqetJgP2F4ualakqAmZtBYe3jWen7gxQRw5xApX6crnFtuB91WC0c3ESBnva+kGSnb3+6pIQDQ==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "dependencies": { - "@hapi/address": "^5.1.1", - "@hapi/formula": "^3.0.2", - "@hapi/hoek": "^11.0.7", - "@hapi/pinpoint": "^2.0.1", - "@hapi/tlds": "^1.1.1", - "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.1.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 20" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/json-bigint": { @@ -2642,6 +8002,40 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -2664,6 +8058,22 @@ "npm": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -2676,19 +8086,20 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", - "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-4.1.0.tgz", + "integrity": "sha512-sbkByqyATKYJP5F4RXj03N5TUNC0QLTjCAZvwTzC4BwJZ8e0/cWxN8YROnyUth2g1/ONWi4eSFHeu6oYalrc3Q==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", - "jose": "^4.15.4", + "jose": "^6.1.3", "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" + "lru-cache": "^11.0.0", + "lru-memoizer": "^3.0.0" }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.12.0 || >= 23.0.0" } }, "node_modules/jws": { @@ -2701,6 +8112,323 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -2722,6 +8450,7 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "devOptional": true, "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -2773,6 +8502,13 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -2786,81 +8522,96 @@ "devOptional": true, "license": "Apache-2.0" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/lru-memoizer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", - "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-3.0.0.tgz", + "integrity": "sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==", "license": "MIT", "dependencies": { "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" + "lru-cache": "^11.0.1" } }, - "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/lru.min": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz", + "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==", + "devOptional": true, + "license": "MIT", "engines": { - "node": ">=10" + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" } }, - "node_modules/mariadb": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.5.4.tgz", - "integrity": "sha512-3m0vdfgRkjle5/9hHl4R7rlVvnDA7QfuFSNkdIAQNROWKOS0J3b9IQK9HaZSCGEVKiDUbFXFiINfC+cmR5P/aA==", - "license": "LGPL-2.1-or-later", + "node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/geojson": "^7946.0.16", - "@types/node": ">=20", - "denque": "^2.1.0", - "iconv-lite": "^0.7.2", - "lru-cache": "^11.5.0" - }, - "engines": { - "node": ">= 20.0.0" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 8" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8.6" } }, "node_modules/mime": { @@ -2876,476 +8627,965 @@ "node": ">=10.0.0" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz", + "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.3", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.5.1" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.5.tgz", + "integrity": "sha512-NFMjXXkyodBJnRrM0A6qxFO1OLUbrYLDhP1ZLetlXnyx41gKOAmVyROibq85j7I8dda4aZMs9L7kmRvvjNf7Cg==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.2.tgz", + "integrity": "sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==", + "license": "MIT", + "dependencies": { + "@next/env": "16.4.2", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.4.2", + "@next/swc-darwin-x64": "16.4.2", + "@next/swc-linux-arm64-gnu": "16.4.2", + "@next/swc-linux-arm64-musl": "16.4.2", + "@next/swc-linux-x64-gnu": "16.4.2", + "@next/swc-linux-x64-musl": "16.4.2", + "@next/swc-win32-arm64-msvc": "16.4.2", + "@next/swc-win32-x64-msvc": "16.4.2", + "sharp": "^0.35.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.59.tgz", + "integrity": "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nodemailer": { + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.1.7.tgz", + "integrity": "sha512-OQa6FDln5fLttmqSbjDiRYvFDOFNrBqfiOwHPlEbURil3b+vPB1v8xpi+Sp46drzNDlsTnNsS502vthmz56r4w==", + "license": "MIT-0", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "license": "MIT", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/moment-timezone": { - "version": "0.5.48", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", - "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], "license": "MIT", - "dependencies": { - "moment": "^2.29.4" - }, "engines": { - "node": "*" + "node": ">=12.20.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "devOptional": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" } }, - "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, "engines": { - "node": "^18 || ^20 || >= 21" + "node": ">= 0.8.0" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], + "node_modules/otplib": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.5.0.tgz", + "integrity": "sha512-RpcC6aq4rANX6MverMuU3pqHVLgMPO7vl6qR8ga7YzoEHlPJknJ58cSVAaf3jBYnasiQVQHXXl2mj/4pYlsVEQ==", "license": "MIT", - "engines": { - "node": ">=10.5.0" + "dependencies": { + "@otplib/core": "13.5.0", + "@otplib/hotp": "13.5.0", + "@otplib/plugin-base32-scure": "13.5.0", + "@otplib/plugin-crypto-noble": "13.5.0", + "@otplib/totp": "13.5.0", + "@otplib/uri": "13.5.0" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "whatwg-url": "^5.0.0" + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">= 6.13.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nodemailer": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.1.tgz", - "integrity": "sha512-c+gU9cL9HLDax3vjxL88kW+6NOgdtEUWaZ+AUtxdJR6LLhf0kGdCLExof7yiKW7zdO9EfXCSIgmhGyFmUM0mYQ==", - "dev": true, - "license": "MIT-0", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "dev": true, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/oauth": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", - "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", - "license": "MIT" + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0", + "optional": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "license": "MIT", - "optional": true, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", + "optional": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=14.0.0" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "devOptional": true, "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "optional": true, "dependencies": { - "ee-first": "1.1.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC", - "dependencies": { - "wrappy": "1" - } + "optional": true }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "pg-connection-string": "^2.14.1", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.1", + "pg-types": "2.2.0", + "pgpass": "1.0.5" }, "engines": { - "node": ">=6" + "node": ">= 16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "pg-cloudflare": "^1.4.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/pg-cloudflare": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "split2": "^4.1.0" } }, - "node_modules/passport": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", - "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" - }, "engines": { - "node": ">= 0.4.0" + "node": ">=8.6" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/passport-google-oauth20": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", - "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "node_modules/pkg-types": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", + "devOptional": true, "license": "MIT", "dependencies": { - "passport-oauth2": "1.x.x" - }, + "confbox": "^0.3.1", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" } }, - "node_modules/passport-oauth2": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", - "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "base64url": "3.x.x", - "oauth": "0.10.x", - "passport-strategy": "1.x.x", - "uid2": "0.0.x", - "utils-merge": "1.x.x" + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 0.4.0" + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" + "type": "individual", + "url": "https://github.com/sponsors/porsager" } }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">=12" } }, - "node_modules/path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "license": "MIT", - "dependencies": { - "process": "^0.11.1", - "util": "^0.10.3" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" - }, - "node_modules/pg-connection-string": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", - "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", - "license": "MIT" + "node_modules/prisma": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.1.tgz", + "integrity": "sha512-GoVeErHGGtsSjsflQ1r2BA3RKTXtxNraQJz8OLkLhoLr7OgCscdx4qw4WMM4HnERYc+a1cq2VnMNbep+vOjNcQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.10.1", + "@prisma/dev": "0.24.17", + "@prisma/engines": "7.10.1", + "@prisma/studio-core": "0.33.0", + "mysql2": "3.24.4", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, "license": "MIT", - "engines": { - "node": ">=10.13.0" + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">= 0.6.0" + "node": ">= 4" } }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/proto3-json-serializer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", - "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-4.1.0.tgz", + "integrity": "sha512-Z0Blk2BTZTPp9ZXOWnRG/P3bkEs53FtMv59sl4xU44xcDdYhKlVO/AqAHTYXAlW9HdislqQxjSornP3457wkag==", "license": "Apache-2.0", "optional": true, "dependencies": { - "protobufjs": "^7.2.5" + "protobufjs": "^7.5.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=22" } }, - "node_modules/proto3-json-serializer/node_modules/protobufjs": { + "node_modules/protobufjs": { "version": "7.6.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", @@ -3369,43 +9609,31 @@ "node": ">=12.0.0" } }, - "node_modules/protobufjs": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", - "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], "license": "MIT" }, "node_modules/qrcode": { @@ -3425,58 +9653,142 @@ "node": ">=10.13.0" } }, - "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.6" + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.7", + "destr": "^2.0.5" + } + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/rate-limit-redis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.3.1.tgz", - "integrity": "sha512-+a1zU8+D7L8siDK9jb14refQXz60vq427VuiplgnaLk9B2LnvGe/APLTfhwb4uNIL7eWVknh8GnRp/unCj+lMA==", + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", "license": "MIT", - "engines": { - "node": ">= 16" - }, "peerDependencies": { - "express-rate-limit": ">= 6" + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "node_modules/react-dom": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" + "scheduler": "^0.28.0" }, - "engines": { - "node": ">= 0.10" + "peerDependencies": { + "react": "^19.3.1" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3493,16 +9805,71 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" }, "engines": { - "node": ">=8.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/remeda": { + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remeda" } }, "node_modules/require-directory": { @@ -3514,12 +9881,76 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.1.tgz", + "integrity": "sha512-Z/nyvABwcRnRD2mMP7Kz6G1dFZZqX1mNErOnxF42OCQKE07b9k9hK9SQxHL3LFEZUdzQaLM4dId1E/E6Vm4WhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -3530,41 +9961,130 @@ "node": ">= 4" } }, - "node_modules/retry-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", - "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", - "license": "MIT" - }, "node_modules/retry-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", - "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-9.1.0.tgz", + "integrity": "sha512-FILyVbis+DTo3KRbtKceqpj1595wIF+XQFW/+AVOBdCQmxwNDgcNXXr2FNM0Ej2qD7zVXbJGfH7lo4jB0wwhKA==", "license": "MIT", "optional": true, "dependencies": { - "@types/request": "^2.48.8", - "extend": "^3.0.2", - "teeny-request": "^9.0.0" + "extend": "^3.0.2" }, "engines": { - "node": ">=14" + "node": ">=22" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "optional": true, "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "devOptional": true, + "license": "Unlicense" + }, + "node_modules/rolldown": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.154.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">= 18" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safe-buffer": { @@ -3587,16 +10107,81 @@ ], "license": "MIT" }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex2": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.2.tgz", + "integrity": "sha512-tAtCGFPPP02VroDVzp4daGi6bC7jWG+41D47GKf18FeH/ubvKQHT9VmhAkvtjNOmJwqtApzWeUpEhkyEYULvmw==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3605,140 +10190,145 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 0.4" } }, - "node_modules/sequelize": { - "version": "6.37.8", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz", - "integrity": "sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/sequelize" - } - ], + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/debug": "^4.1.8", - "@types/validator": "^13.7.17", - "debug": "^4.3.4", - "dottie": "^2.0.6", - "inflection": "^1.13.4", - "lodash": "^4.17.21", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "pg-connection-string": "^2.6.1", - "retry-as-promised": "^7.0.4", - "semver": "^7.5.4", - "sequelize-pool": "^7.1.0", - "toposort-class": "^1.0.1", - "uuid": "^8.3.2", - "validator": "^13.9.0", - "wkx": "^0.5.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=10.0.0" + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, - "peerDependenciesMeta": { - "ibm_db": { - "optional": true - }, - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "snowflake-sdk": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { "optional": true } } }, - "node_modules/sequelize-pool": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", - "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "devOptional": true, "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "node": ">=8" } }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "devOptional": true, "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, "engines": { - "node": ">= 18" + "node": ">=8" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3758,6 +10348,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3774,6 +10365,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3792,6 +10384,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3807,38 +10400,79 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=14" }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/speakeasy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz", - "integrity": "sha512-lW2A2s5LKi8rwu77ewisuUOtlCydF/hmQSOJjpTqTj1gZLkNgTaYnyvfxy2WBr4T/h+9c4g8HIITfj83OkFQFw==", + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sql-escaper": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", + "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "base32.js": "0.0.1" - }, "engines": { - "node": ">= 0.10.0" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, "node_modules/stream-events": { @@ -3879,84 +10513,342 @@ "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz", + "integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-11.1.1.tgz", + "integrity": "sha512-I5OLOkPN3QDN2l1H/eGueuKaq32CdAcXt91WOF/bOfadtZD1WQ8/Fk28WsqDcGjIyhAW/GQDagYhVEjhuZUFyQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" + }, + "engines": { + "node": ">=22" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/tinybench": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", + "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=20.0.0" } }, - "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=18" + } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=4" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/teeny-request": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", - "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.9", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/teeny-request/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/to-regex-range": { @@ -3972,37 +10864,59 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", - "license": "MIT" + "node_modules/ts-essentials": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.2.1.tgz", + "integrity": "sha512-+Id1fRkuir+CsgK2x04/icS2b4V1hQmq7ObzIrDjhN0ozfRYivnP7aaKMVJfLApQm0trjR39A6NIMVchiB9Erw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } }, "node_modules/tslib": { "version": "2.8.1", @@ -4010,72 +10924,231 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, "license": "MIT", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">= 18" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dev": true, "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uid2": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", - "license": "MIT" + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "license": "MIT", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "inherits": "2.0.3" + "punycode": "^2.1.0" } }, "node_modules/util-deprecate": { @@ -4085,53 +11158,261 @@ "license": "MIT", "optional": true }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" + "node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/vite": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/uuid": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", - "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "node_modules/vite/node_modules/postcss": { + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" + "peer": true, + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/validator": { - "version": "13.15.35", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", - "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "node_modules/vitest": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", + "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", + "dev": true, "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.4.3", + "obug": "^3.0.0", + "picomatch": "^4.0.7", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">= 0.10" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vitest-mock-extended": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-5.1.1.tgz", + "integrity": "sha512-k5Ji2+t4+nsdepXeakCkilyKydtgaULtP0HsjwQGIZsYr5hDTqrwCtC0+Sh0YBKUS7cY/Wxplf7SVjcZstTIMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-essentials": "^10.2.1" + }, + "peerDependencies": { + "typescript": "3.x || 4.x || 5.x || 6.x || 7.x", + "vitest": ">=4.0.0" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vitest/node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -4141,13 +11422,6 @@ "node": ">= 8" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true - }, "node_modules/websocket-driver": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", @@ -4171,15 +11445,87 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-module": { @@ -4188,40 +11534,99 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "license": "ISC" }, - "node_modules/wkx": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", - "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "devOptional": true, + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", "funding": [ { "type": "github", @@ -4229,69 +11634,111 @@ } ], "license": "MIT", + "optional": true, "engines": { "node": ">=16.0.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", + "optional": true, "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, + "optional": true, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zeptomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index b878742..dd34073 100644 --- a/package.json +++ b/package.json @@ -1,66 +1,69 @@ { - "name": "grp-a-isp-cliffkoome", - "version": "1.0.0", - "description": "[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.[...]", - "main": "index.js", - "scripts": { - "dev": "nodemon server/server.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/cliffkoome/GRP-A-ISP-cliffkoome.git" - }, - "keywords": [], - "author": "", + "name": "matconnect", + "version": "2.0.0", + "private": true, + "type": "module", + "description": "MatConnect — IoT-enabled real-time matatu arrival display (Next.js + Prisma + Neon Postgres)", "license": "ISC", - "type": "commonjs", - "bugs": { - "url": "https://github.com/cliffkoome/GRP-A-ISP-cliffkoome/issues" + "engines": { + "node": ">=22" + }, + "scripts": { + "dev": "next dev", + "build": "prisma generate && next build", + "start": "next start", + "lint": "eslint .", + "typecheck": "next typegen && tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "test:integration": "vitest run --config vitest.integration.config.ts", + "postinstall": "prisma generate", + "db:generate": "prisma generate", + "db:validate": "prisma validate", + "db:format": "prisma format", + "db:migrate:dev": "prisma migrate dev", + "db:migrate:create": "prisma migrate dev --create-only", + "db:migrate:deploy": "prisma migrate deploy", + "db:migrate:status": "prisma migrate status", + "db:migrate:check": "node scripts/check-migrations.mjs", + "db:seed": "prisma db seed", + "db:studio": "prisma studio", + "eta:worker": "tsx --conditions=react-server scripts/eta-worker.ts" }, - "homepage": "https://github.com/cliffkoome/GRP-A-ISP-cliffkoome#readme", "dependencies": { - "africastalking": "^0.7.7", - "axios": ">=1.18.0", - "bcrypt": "^6.0.0", - "body-parser": ">=2.3.0", - "brace-expansion": ">=5.0.9", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^17.2.2", - "express": "^5.1.0", - "fast-xml-parser": ">=5.7.0", - "firebase-admin": "^13.5.0", - "form-data": ">=4.0.6", - "jsonwebtoken": "^9.0.2", - "joi": ">=18.2.1", - "lodash": ">=4.18.0", - "mariadb": ">=3.4.6", - "node-forge": ">=1.4.0", - "nodemailer": ">=9.0.1", - "passport": "^0.7.0", - "rate-limit-redis": "^4.2.0", - "passport-google-oauth20": "^2.0.0", - "path": "^0.12.7", - "protobufjs": ">=7.6.5", + "@neondatabase/serverless": "^1.2.0", + "@prisma/adapter-neon": "^7.10.1", + "@prisma/adapter-pg": "^7.10.1", + "@prisma/client": "^7.10.1", + "bcryptjs": "^3.0.3", + "chart.js": "^4.5.1", + "firebase-admin": "^14.5.0", + "jose": "^6.2.12", + "next": "^16.4.2", + "nodemailer": "^10.1.7", + "otplib": "^13.5.0", + "pg": "^8.23.1", "qrcode": "^1.5.4", - "sequelize": "^6.37.7", - "speakeasy": "^2.0.0", - "uuid": ">=11.1.1", - "websocket-driver": ">=0.7.5" + "react": "^19.3.1", + "react-chartjs-2": "^5.3.1", + "react-dom": "^19.3.1", + "server-only": "^0.0.1", + "zod": "^4.6.7" }, "devDependencies": { - "axios": ">=1.18.0", - "body-parser": ">=2.3.0", - "brace-expansion": ">=5.0.9", - "fast-xml-parser": ">=5.7.0", - "form-data": ">=4.0.6", - "joi": ">=18.2.1", - "lodash": ">=4.18.0", - "node-forge": ">=1.4.0", - "nodemailer": ">=9.0.1", - "nodemon": "^3.1.10", - "protobufjs": ">=7.6.5", - "uuid": ">=11.1.1", - "websocket-driver": ">=0.7.5" + "@types/node": "^22.20.5", + "@types/nodemailer": "^8.0.2", + "@types/pg": "^8.23.1", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "dotenv": "^18.0.8", + "eslint": "^9.39.5", + "eslint-config-next": "^16.4.2", + "prisma": "^7.10.1", + "tsx": "^4.23.15", + "typescript": "^6.0.3", + "vitest": "^5.0.5", + "vitest-mock-extended": "^5.1.1" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..6df99e5 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,25 @@ +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +/** + * Prisma CLI configuration. + * + * Neon exposes two connection strings: + * - DATABASE_URL: the *pooled* (PgBouncer, `-pooler` host) URL used by the app at runtime. + * - DIRECT_URL: the *direct* (unpooled) URL. Migrations need a direct session + * (advisory locks, DDL in transactions), so the CLI always prefers it. + * + * `prisma generate` does not need a database, so a missing URL is allowed here; commands + * that do need one (`migrate`, `db`, `studio`) will fail with a clear Prisma error. + */ +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + seed: "tsx prisma/seed.ts", + }, + datasource: { + url: process.env.DIRECT_URL ?? process.env.DATABASE_URL ?? "", + shadowDatabaseUrl: process.env.SHADOW_DATABASE_URL, + }, +}); diff --git a/prisma/migrations/20261019000000_init/migration.sql b/prisma/migrations/20261019000000_init/migration.sql new file mode 100644 index 0000000..ba92cdf --- /dev/null +++ b/prisma/migrations/20261019000000_init/migration.sql @@ -0,0 +1,205 @@ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "public"; + +-- CreateEnum +CREATE TYPE "user_role" AS ENUM ('Passenger', 'Admin', 'MatAdmin'); + +-- CreateTable +CREATE TABLE "users" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT, + "role" "user_role" NOT NULL DEFAULT 'Passenger', + "google_id" TEXT, + "phone_number" TEXT, + "profile_picture_url" TEXT, + "two_factor_secret" TEXT, + "two_factor_enabled" BOOLEAN NOT NULL DEFAULT false, + "disabled" BOOLEAN NOT NULL DEFAULT false, + "tokens_valid_from" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "stages" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "latitude" DOUBLE PRECISION NOT NULL, + "longitude" DOUBLE PRECISION NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "stages_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "vehicles" ( + "id" SERIAL NOT NULL, + "car_id" TEXT NOT NULL, + "plate_number" TEXT NOT NULL, + "owner_id" INTEGER, + "last_latitude" DOUBLE PRECISION, + "last_longitude" DOUBLE PRECISION, + "is_at_stage" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + "deleted_at" TIMESTAMPTZ(3), + + CONSTRAINT "vehicles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "stage_vehicles" ( + "stage_id" INTEGER NOT NULL, + "vehicle_id" INTEGER NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "stage_vehicles_pkey" PRIMARY KEY ("stage_id","vehicle_id") +); + +-- CreateTable +CREATE TABLE "stage_subscriptions" ( + "user_id" INTEGER NOT NULL, + "stage_id" INTEGER NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "stage_subscriptions_pkey" PRIMARY KEY ("user_id","stage_id") +); + +-- CreateTable +CREATE TABLE "daily_distances" ( + "id" SERIAL NOT NULL, + "vehicle_id" INTEGER NOT NULL, + "date" DATE NOT NULL, + "distance_covered" DOUBLE PRECISION NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "daily_distances_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "daily_trips" ( + "id" SERIAL NOT NULL, + "vehicle_id" INTEGER NOT NULL, + "date" DATE NOT NULL, + "trip_count" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "daily_trips_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "feedback" ( + "id" SERIAL NOT NULL, + "vehicle_id" INTEGER NOT NULL, + "user_id" INTEGER NOT NULL, + "rating" INTEGER NOT NULL, + "comment" TEXT, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "feedback_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "job_locks" ( + "name" TEXT NOT NULL, + "locked_until" TIMESTAMPTZ(3) NOT NULL, + "locked_by" TEXT, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "job_locks_pkey" PRIMARY KEY ("name") +); + +-- CreateTable +CREATE TABLE "revoked_tokens" ( + "jti" TEXT NOT NULL, + "expires_at" TIMESTAMPTZ(3) NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "revoked_tokens_pkey" PRIMARY KEY ("jti") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_google_id_key" ON "users"("google_id"); + +-- CreateIndex +CREATE INDEX "users_role_idx" ON "users"("role"); + +-- CreateIndex +CREATE UNIQUE INDEX "stages_name_key" ON "stages"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "vehicles_car_id_key" ON "vehicles"("car_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "vehicles_plate_number_key" ON "vehicles"("plate_number"); + +-- CreateIndex +CREATE INDEX "vehicles_owner_id_idx" ON "vehicles"("owner_id"); + +-- CreateIndex +CREATE INDEX "vehicles_deleted_at_idx" ON "vehicles"("deleted_at"); + +-- CreateIndex +CREATE INDEX "stage_vehicles_vehicle_id_idx" ON "stage_vehicles"("vehicle_id"); + +-- CreateIndex +CREATE INDEX "stage_subscriptions_stage_id_idx" ON "stage_subscriptions"("stage_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "daily_distances_vehicle_id_date_key" ON "daily_distances"("vehicle_id", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "daily_trips_vehicle_id_date_key" ON "daily_trips"("vehicle_id", "date"); + +-- CreateIndex +CREATE INDEX "feedback_vehicle_id_created_at_idx" ON "feedback"("vehicle_id", "created_at"); + +-- CreateIndex +CREATE INDEX "feedback_user_id_idx" ON "feedback"("user_id"); + +-- CreateIndex +CREATE INDEX "feedback_created_at_idx" ON "feedback"("created_at"); + +-- CreateIndex +CREATE INDEX "revoked_tokens_expires_at_idx" ON "revoked_tokens"("expires_at"); + +-- AddForeignKey +ALTER TABLE "vehicles" ADD CONSTRAINT "vehicles_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "stage_vehicles" ADD CONSTRAINT "stage_vehicles_stage_id_fkey" FOREIGN KEY ("stage_id") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "stage_vehicles" ADD CONSTRAINT "stage_vehicles_vehicle_id_fkey" FOREIGN KEY ("vehicle_id") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "stage_subscriptions" ADD CONSTRAINT "stage_subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "stage_subscriptions" ADD CONSTRAINT "stage_subscriptions_stage_id_fkey" FOREIGN KEY ("stage_id") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "daily_distances" ADD CONSTRAINT "daily_distances_vehicle_id_fkey" FOREIGN KEY ("vehicle_id") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "daily_trips" ADD CONSTRAINT "daily_trips_vehicle_id_fkey" FOREIGN KEY ("vehicle_id") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_vehicle_id_fkey" FOREIGN KEY ("vehicle_id") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Manual addition: the Prisma schema cannot express CHECK constraints, and Prisma ignores them when diffing. +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_rating_range" CHECK ("rating" BETWEEN 1 AND 5); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..fd6e3a6 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,191 @@ +// MatConnect database schema (PostgreSQL on Neon). +// +// Conventions (see docs/MIGRATIONS.md): +// - Models are PascalCase/singular in TypeScript, tables are snake_case/plural in Postgres (@@map). +// - Columns are snake_case in Postgres (@map) and camelCase in TypeScript. +// - Every table has created_at; mutable tables also have updated_at. +// - Timestamps are `timestamptz` (@db.Timestamptz(3)); calendar days are `date` (@db.Date). +// - Foreign keys always declare onDelete explicitly. +// - Never edit a migration that has been merged to main; add a new one instead. + +generator client { + provider = "prisma-client" + output = "../src/generated/prisma" +} + +datasource db { + provider = "postgresql" +} + +enum Role { + Passenger + Admin + MatAdmin + + @@map("user_role") +} + +model User { + id Int @id @default(autoincrement()) + name String + /// Always stored lower-cased (normalised at the validation layer). + email String @unique + /// bcrypt hash. Null for accounts created through Google sign-in. + password String? + role Role @default(Passenger) + googleId String? @unique @map("google_id") + /// E.164 format, e.g. +254712345678. + phoneNumber String? @map("phone_number") + profilePictureUrl String? @map("profile_picture_url") + twoFactorSecret String? @map("two_factor_secret") + twoFactorEnabled Boolean @default(false) @map("two_factor_enabled") + disabled Boolean @default(false) + /// Tokens issued before this instant are rejected (logout-everywhere / block / password reset). + tokensValidFrom DateTime @default(now()) @map("tokens_valid_from") @db.Timestamptz(3) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + ownedVehicles Vehicle[] + feedback Feedback[] + subscriptions StageSubscription[] + + @@index([role]) + @@map("users") +} + +model Stage { + id Int @id @default(autoincrement()) + name String @unique + latitude Float + longitude Float + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + vehicles StageVehicle[] + subscribers StageSubscription[] + + @@map("stages") +} + +model Vehicle { + id Int @id @default(autoincrement()) + /// Identifier reported by the GPS device (key under `locations/` in Firebase). + carId String @unique @map("car_id") + plateNumber String @unique @map("plate_number") + ownerId Int? @map("owner_id") + lastLatitude Float? @map("last_latitude") + lastLongitude Float? @map("last_longitude") + /// True while the vehicle is inside an assigned stage's arrival radius (used for trip counting). + isAtStage Boolean @default(false) @map("is_at_stage") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + /// Soft delete marker. Always filter with `deletedAt: null` (see src/server/modules/vehicles). + deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3) + + owner User? @relation(fields: [ownerId], references: [id], onDelete: SetNull) + stages StageVehicle[] + dailyDistances DailyDistance[] + dailyTrips DailyTrip[] + feedback Feedback[] + + @@index([ownerId]) + @@index([deletedAt]) + @@map("vehicles") +} + +/// Which vehicles serve which stages (the route a vehicle runs). +model StageVehicle { + stageId Int @map("stage_id") + vehicleId Int @map("vehicle_id") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + + stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade) + vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) + + @@id([stageId, vehicleId]) + @@index([vehicleId]) + @@map("stage_vehicles") +} + +/// One-shot SMS alert subscriptions: removed automatically after the alert is delivered. +model StageSubscription { + userId Int @map("user_id") + stageId Int @map("stage_id") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade) + + @@id([userId, stageId]) + @@index([stageId]) + @@map("stage_subscriptions") +} + +model DailyDistance { + id Int @id @default(autoincrement()) + vehicleId Int @map("vehicle_id") + date DateTime @db.Date + /// Metres travelled on `date` (UTC). + distanceCovered Float @default(0) @map("distance_covered") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) + + @@unique([vehicleId, date]) + @@map("daily_distances") +} + +model DailyTrip { + id Int @id @default(autoincrement()) + vehicleId Int @map("vehicle_id") + date DateTime @db.Date + tripCount Int @default(0) @map("trip_count") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) + + @@unique([vehicleId, date]) + @@map("daily_trips") +} + +model Feedback { + id Int @id @default(autoincrement()) + vehicleId Int @map("vehicle_id") + userId Int @map("user_id") + /// 1–5, enforced by the `feedback_rating_range` CHECK constraint in the init migration. + rating Int + comment String? + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([vehicleId, createdAt]) + @@index([userId]) + @@index([createdAt]) + @@map("feedback") +} + +/// Lease-style mutex for background jobs (works through Neon's PgBouncer pooler, unlike session +/// advisory locks), so overlapping cron invocations or several app instances never run a job twice. +model JobLock { + name String @id + lockedUntil DateTime @map("locked_until") @db.Timestamptz(3) + lockedBy String? @map("locked_by") + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + + @@map("job_locks") +} + +/// Revoked JWT IDs (logout and refresh-token rotation). Rows past `expiresAt` are purged by the ETA cycle. +model RevokedToken { + jti String @id + expiresAt DateTime @map("expires_at") @db.Timestamptz(3) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + + @@index([expiresAt]) + @@map("revoked_tokens") +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..3abdfd6 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,72 @@ +/** + * Development seed — idempotent, safe to re-run: npm run db:seed + * + * Creates one account per role (password: `matconnect123`), a few Nairobi stages and vehicles. + * Never run against production: it refuses when NODE_ENV=production. + */ +import "dotenv/config"; +import { PrismaPg } from "@prisma/adapter-pg"; +import bcrypt from "bcryptjs"; +import pg from "pg"; +import { PrismaClient } from "../src/generated/prisma/client.ts"; + +if (process.env.NODE_ENV === "production") { + console.error("Refusing to seed with NODE_ENV=production."); + process.exit(1); +} + +const connectionString = process.env.DIRECT_URL ?? process.env.DATABASE_URL; +if (!connectionString) throw new Error("Set DATABASE_URL (or DIRECT_URL) before seeding."); + +// Same UTC session rule as src/server/db.ts (see createUtcPool there). +const pool = new pg.Pool({ connectionString, onConnect: async (client) => void (await client.query("SET TIME ZONE 'UTC'")) }); +const db = new PrismaClient({ adapter: new PrismaPg(pool) }); + +async function main() { + const password = await bcrypt.hash("matconnect123", 10); + const user = (email: string, name: string, role: "Admin" | "MatAdmin" | "Passenger", phoneNumber?: string) => + db.user.upsert({ + where: { email }, + update: {}, + create: { email, name, role, password, phoneNumber }, + }); + + const [, owner] = await Promise.all([ + user("admin@matconnect.local", "System Admin", "Admin"), + user("owner@matconnect.local", "Matatu Owner", "MatAdmin"), + user("passenger@matconnect.local", "Pat Passenger", "Passenger", "+254700000000"), + ]); + + const stages = await Promise.all( + [ + { name: "Kenyatta Avenue", latitude: -1.2841, longitude: 36.8233 }, + { name: "Kencom", latitude: -1.2864, longitude: 36.8253 }, + { name: "Westlands", latitude: -1.2676, longitude: 36.8108 }, + { name: "Archives", latitude: -1.2836, longitude: 36.8265 }, + ].map((s) => db.stage.upsert({ where: { name: s.name }, update: {}, create: s })), + ); + + const vehicles = await Promise.all( + [ + { carId: "CAR_001", plateNumber: "KDH 420X" }, + { carId: "CAR_002", plateNumber: "KCL 112A" }, + { carId: "CAR_003", plateNumber: "KDJ 899B" }, + ].map((v) => db.vehicle.upsert({ where: { carId: v.carId }, update: {}, create: { ...v, ownerId: owner.id } })), + ); + + // Route: every vehicle serves Westlands ↔ Kencom. + const route = stages.filter((s) => ["Westlands", "Kencom"].includes(s.name)); + await db.stageVehicle.createMany({ + data: vehicles.flatMap((v) => route.map((s) => ({ stageId: s.id, vehicleId: v.id }))), + skipDuplicates: true, + }); + + console.log(`Seeded ${stages.length} stages, ${vehicles.length} vehicles and 3 users (password: matconnect123).`); +} + +main() + .catch((error) => { + console.error(error); + process.exitCode = 1; + }) + .finally(() => db.$disconnect()); diff --git a/public/admin-dashboard.html b/public/admin-dashboard.html deleted file mode 100644 index 7af9c9b..0000000 --- a/public/admin-dashboard.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - Admin Dashboard - MatConnect - - - - - - - -
- - -
-
-

Admin Dashboard

-

Welcome back! Here's an overview of the system.

-
- - -
-
-
- directions_bus -
-
-

Total Vehicles

- -- -
-
-
-
- signal_cellular_alt -
-
-

Online Vehicles

- -- -
-
-
-
- pin_drop -
-
-

Configured Stages

- -- -
-
-
- - -
-
-
-
-

Live Vehicle Status

- View All -
-
- - - - - - - - - - - - - -
VehicleRouteStatusLocation
Loading vehicle data...
-
-
-
- - -
-
-
- - - \ No newline at end of file diff --git a/public/admin-settings.html b/public/admin-settings.html deleted file mode 100644 index 0771311..0000000 --- a/public/admin-settings.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Admin Settings - - - - - - -
- - -
- - -
- - -
-
-
-

Manage Routes

- -
-
- - - - - - - - - - - - - - - - - - - - - - - -
Route NameStart StageEnd StageActions
Route 1Stage AStage BEdit
Route 2Stage CStage DEdit
-
-
-
- - - -
-
-
-

Manage Stages

- -
-
- - - - - - - - - - - - - - - - - - - - -
Stage NameLocationActions
Stage ALocation 1Edit
Stage BLocation 2Edit
-
-
-
- - - -
-
-
-

Manage Devices

- -
-
- - - - - - - - - - - - - - - - - - - - -
Device IDStatusActions
Device 1ActiveEdit
Device 2InactiveEdit
-
-
-
-
-
-
- - \ No newline at end of file diff --git a/public/auth-success.html b/public/auth-success.html deleted file mode 100644 index 125ea17..0000000 --- a/public/auth-success.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Authenticating... - - - -

Please wait, you are being redirected...

- - - \ No newline at end of file diff --git a/public/feedback-page.html b/public/feedback-page.html deleted file mode 100644 index 2ee12be..0000000 --- a/public/feedback-page.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - Feedback - - - - - - - - - -
- - -
- -
- -
-

© 2025 Ma3Route. All rights reserved.

-
-
- - \ No newline at end of file diff --git a/public/forgot-password.html b/public/forgot-password.html deleted file mode 100644 index fcdea25..0000000 --- a/public/forgot-password.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - MatConnect - Reset Password - - - - - - - -
-
-
-

Reset your Password

-

Enter your email and we'll send you a reset link.

-
- -
-
- -
- - -
- - -
-
-
- -
-
- -
-
- - - \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index ac21262..0000000 --- a/public/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - MatConnect - - - - - - - - - - - - - -
-
-

Know when your matatu is arriving in real-time.

-

- MatConnect provides real-time arrival information for matatus, helping you plan your commute efficiently and - avoid unnecessary waiting. -

-
- - -
-
- -
-
-

Features

-

Everything you need for a stress-free commute.

-
-
-
- -
-

Real-Time ETA

-

Get accurate estimated arrival times for your matatu, updated in real-time based on - current traffic and route conditions.

-
-
- -
- -
-

Stage Information

-

Access detailed information about matatu stages, including routes, stops, and available - services.

-
-
- -
- -
-

Reliable Updates

-

Recieve timely notifications and updates about your matatu's progress, ensuring you're - always informed.

-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/public/login.html b/public/login.html deleted file mode 100644 index 6604ae1..0000000 --- a/public/login.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - MatConnect Login - - - - - - - -
-
-
-

Welcome Back to MatConnect

-

Sign in to continue

-
- -
- -
-
- - -
-
- - -
- - -
- -
- -
-
-
- OR -
-
- -
- -
-
- - - \ No newline at end of file diff --git a/public/manage-stages.html b/public/manage-stages.html deleted file mode 100644 index a728a56..0000000 --- a/public/manage-stages.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - Manage Stages - - - - - - - -
- - -
- -
-
-

Stages

-
-
-
    - -

    Loading stages...

    -
-
-
- -
-
- - -
-
-

Select a Stage

-

Details will appear here.

-
-
-

Assigned Vehicles

-
    - -

    Select a stage to see assigned vehicles.

    -
-
-
-
- - - -
-
-
-
-
- - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/manage-users.html b/public/manage-users.html deleted file mode 100644 index 8183622..0000000 --- a/public/manage-users.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - Manage Users - MatConnect - - - - - - - - -
- - -
-
-
-

System Users

-
- -
- - - - -
- -
-
-
- - - - - - - - - - - - - - -
NameEmailRoleStatusActions
Loading users...
-
-
-
-
- - - - - \ No newline at end of file diff --git a/public/mat-admin-dashboard.html b/public/mat-admin-dashboard.html deleted file mode 100644 index b673cc4..0000000 --- a/public/mat-admin-dashboard.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - Matatu Admin Dashboard - MatConnect - - - - - - - -
- - -
-
-

My Dashboard

-

Welcome back! Here's an overview of your fleet's performance today.

-
- - -
-
-
- directions_bus -
-
-

My Vehicles

- -- -
-
-
-
- social_distance -
-
-

Total Distance Today

- -- km -
-
-
-
- trip_origin -
-
-

Total Trips Today

- -- -
-
-
- - -
-
-
-
-

Live Vehicle Status

- View All Stats -
-
- - - - - - - - - - - - -
VehicleStatusCurrent Location
Loading vehicle data...
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/public/mat-admin-vehicles.html b/public/mat-admin-vehicles.html deleted file mode 100644 index 3cf188a..0000000 --- a/public/mat-admin-vehicles.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - My Vehicles - MatConnect - - - - - - - - - - -
- - -
-
-

My Vehicles

-

Detailed statistics and feedback for your fleet.

-
- -
-
- - -
-
-
- - - -
- -
-
- -
- -
-

Loading vehicle stats...

-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/public/scripts/admin-dashboard.js b/public/scripts/admin-dashboard.js deleted file mode 100644 index 1777909..0000000 --- a/public/scripts/admin-dashboard.js +++ /dev/null @@ -1,171 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - document.querySelector('.avatar').src = '/images/pfp.jpg'; - - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const navList = mainNav.querySelector('ul'); - const userMenu = document.querySelector('.user-menu'); - const logoutLinkDesktop = document.getElementById('logout-link-desktop'); - - const vehicleTableBody = document.querySelector('.data-table tbody'); - const feedbackList = document.getElementById('feedback-list'); - const accessToken = localStorage.getItem('accessToken'); - - // Stat card elements - const totalVehiclesStat = document.getElementById('total-vehicles-stat'); - const onlineVehiclesStat = document.getElementById('online-vehicles-stat'); - const totalStagesStat = document.getElementById('total-stages-stat'); - - - - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - return response.json(); - }; - - function createVehicleRow(vehicle) { - const statusClass = vehicle.status.toLowerCase() === 'online' ? 'badge-success' : 'badge-danger'; - let locationCell = vehicle.location || 'N/A'; - - // If we have coordinates, make the location a link to Google Maps - if (vehicle.latitude && vehicle.longitude) { - const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${vehicle.latitude},${vehicle.longitude}`; - locationCell = `${vehicle.location}`; - } - - return ` - - ${vehicle.plateNumber} - ${vehicle.route} - ${vehicle.status} - ${locationCell} - - `; - } - - async function fetchAndDisplayDashboardData() { - try { - const data = await apiFetch('/api/admin/dashboard-data'); - - // Update vehicle table and stats - if (data.vehicles && data.vehicles.length > 0) { - vehicleTableBody.innerHTML = data.vehicles.map(createVehicleRow).join(''); - totalVehiclesStat.textContent = data.vehicles.length; - const onlineCount = data.vehicles.filter(v => v.status.toLowerCase() === 'online').length; - onlineVehiclesStat.textContent = onlineCount; - } else { - vehicleTableBody.innerHTML = 'No vehicles found.'; - totalVehiclesStat.textContent = 0; - onlineVehiclesStat.textContent = 0; - } - - // Update stages stat - const stages = await apiFetch('/api/stages'); - totalStagesStat.textContent = stages.length || 0; - - } catch (error) { - console.error('Error fetching dashboard data:', error); - if (vehicleTableBody) { - vehicleTableBody.innerHTML = `Could not load vehicle data.`; - } - } - } - - async function fetchAndDisplayFeedback() { - try { - const feedbacks = await apiFetch('/api/admin/feedback'); - if (feedbacks.length > 0) { - feedbackList.innerHTML = feedbacks.map(item => { - const stars = Array(5).fill(0).map((_, i) => - `star` - ).join(''); - const timeAgo = new Date(item.createdAt).toLocaleDateString(); - - return ` - - `; - }).join(''); - } else { - feedbackList.innerHTML = '

No recent feedback.

'; - } - } catch (error) { - console.error('Error fetching feedback:', error); - feedbackList.innerHTML = '

Could not load feedback.

'; - } - } - - const logoutUser = async () => { - try { - await apiFetch('/api/auth/logout', { method: 'POST' }); - } catch (error) { - console.error('Logout failed, but clearing session anyway.'); - } - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - }; - - const initializeApp = async () => { - // Auth check - const role = localStorage.getItem('role'); - if (!accessToken || role !== 'Admin') { - window.location.href = '/login.html'; - return; - } - - // --- Mobile Menu Toggle --- - mobileMenuBtn.addEventListener('click', () => { - mainNav.classList.toggle('is-active'); - }); - - // --- Desktop User Menu Toggle --- - if (userMenu) { - userMenu.addEventListener('click', (e) => { - // Prevents the document click listener from firing immediately - e.stopPropagation(); - userMenu.classList.toggle('is-active'); - }); - } - - // --- Add Logout Link to Nav --- - const logoutLi = document.createElement('li'); - logoutLi.innerHTML = `Logout`; - navList.appendChild(logoutLi); - logoutLi.querySelector('.logout-link').addEventListener('click', logoutUser); - logoutLinkDesktop.addEventListener('click', logoutUser); - }; - - initializeApp(); - - // Fetch data immediately and then set an interval to refresh - fetchAndDisplayDashboardData(); - setInterval(fetchAndDisplayDashboardData, 60000); // Refresh every 60 seconds - - fetchAndDisplayFeedback(); - - // Close user menu when clicking outside - document.addEventListener('click', () => { - if (userMenu && userMenu.classList.contains('is-active')) { - userMenu.classList.remove('is-active'); - } - }); -}); \ No newline at end of file diff --git a/public/scripts/feedback-page.js b/public/scripts/feedback-page.js deleted file mode 100644 index 3cf1e4f..0000000 --- a/public/scripts/feedback-page.js +++ /dev/null @@ -1,158 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const accessToken = localStorage.getItem('accessToken'); - const ratingStarsContainer = document.querySelector('.rating-stars'); - const profileIcon = document.querySelector('.profile-icon'); - - const feedbackForm = document.getElementById('feedback-form'); - const vehicleSelect = document.getElementById('vehicle-select'); - // Protect the page: redirect to login if not authenticated - if (!accessToken) { - window.location.href = '/login.html'; - return; - } - - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const navList = mainNav.querySelector('ul'); - const headerActions = document.querySelector('.header-actions'); - - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - return response.json(); - }; - - // --- Mobile Menu Toggle --- - mobileMenuBtn.addEventListener('click', () => { - mainNav.classList.toggle('is-active'); - }); - - // --- Logout Functionality --- - function logoutUser() { - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - } - - // Add logout link to mobile menu - const logoutLi = document.createElement('li'); - const logoutMobileLink = document.createElement('a'); - logoutMobileLink.href = '#'; - logoutMobileLink.textContent = 'Logout'; - logoutMobileLink.addEventListener('click', (e) => { - e.preventDefault(); - logoutUser(); - }); - logoutLi.appendChild(logoutMobileLink); - navList.appendChild(logoutLi); - - // Add logout button for desktop view - const logoutDesktopBtn = document.createElement('button'); - logoutDesktopBtn.innerHTML = ''; - logoutDesktopBtn.title = 'Logout'; - logoutDesktopBtn.classList.add('logout-btn-desktop'); - logoutDesktopBtn.addEventListener('click', logoutUser); - // Add it before the profile icon - headerActions.insertBefore(logoutDesktopBtn, headerActions.querySelector('.user-profile')); - - // --- Star Rating Functionality --- - if (ratingStarsContainer) { - const stars = ratingStarsContainer.querySelectorAll('label'); - const starInputs = ratingStarsContainer.querySelectorAll('input[type="radio"]'); - - starInputs.forEach(input => { - input.addEventListener('change', () => { - const selectedValue = parseInt(input.value, 10); - stars.forEach((star, index) => { - // The labels are in reverse order in the DOM (5, 4, 3, 2, 1). - // The visual order is (1, 2, 3, 4, 5) due to `flex-direction: row-reverse`. - const starValue = parseInt(star.htmlFor.replace('star', ''), 10); - star.classList.toggle('is-selected', starValue <= selectedValue); - }); - }); - }); - } - - async function populateVehicleDropdown() { - try { - const vehicles = await apiFetch('/api/feedback/vehicles'); - if (vehicles.length > 0) { - vehicleSelect.innerHTML = ''; - vehicles.forEach(vehicle => { - const option = document.createElement('option'); - option.value = vehicle.id; - option.textContent = vehicle.plateNumber; - vehicleSelect.appendChild(option); - }); - } else { - vehicleSelect.innerHTML = ''; - } - } catch (error) { - console.error('Error fetching vehicles:', error); - vehicleSelect.innerHTML = ''; - } - } - - async function handleFeedbackSubmit(event) { - event.preventDefault(); - const formData = new FormData(feedbackForm); - const vehicleId = formData.get('vehicle'); - const rating = formData.get('rating'); - const comment = formData.get('comments'); - - if (!vehicleId) { - alert('Please select a vehicle.'); - return; - } - if (!rating) { - alert('Please provide a star rating.'); - return; - } - - try { - const response = await apiFetch('/api/feedback', { - method: 'POST', - body: JSON.stringify({ vehicleId, rating: parseInt(rating), comment }), - }); - alert(response.message); - feedbackForm.reset(); - } catch (error) { - alert(`Error submitting feedback: ${error.message}`); - } - } - - async function fetchProfileImage() { - if (!profileIcon) return; - try { - const res = await fetch('/api/auth/me/passenger', { - headers: { Authorization: `Bearer ${accessToken}` } - }); - - if (!res.ok) throw new Error('Failed to fetch profile image.'); - - const { profilePictureUrl } = await res.json(); - profileIcon.src = profilePictureUrl || './images/pfp.jpg'; - profileIcon.refferrerPolicy = 'no-referrer'; - } catch (err) { - console.error('Error fetching profile image:', err); - profileIcon.src = './images/pfp.jpg'; - } - } - - fetchProfileImage(); - - feedbackForm.addEventListener('submit', handleFeedbackSubmit); - populateVehicleDropdown(); -}); \ No newline at end of file diff --git a/public/scripts/forgot-password.js b/public/scripts/forgot-password.js deleted file mode 100644 index 666c991..0000000 --- a/public/scripts/forgot-password.js +++ /dev/null @@ -1,102 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const form = document.getElementById('reset-form'); - const emailInput = document.getElementById('email-address'); - const resetFields = document.getElementById('reset-fields'); - const tokenInput = document.getElementById('reset-token'); - const userIdInput = document.getElementById('user-id'); - const passwordInput = document.getElementById('new-password'); - const submitButton = document.getElementById('submit-button'); - const messageArea = document.getElementById('message-area'); - const subtitle = document.getElementById('subtitle-text'); - - // This flag tracks which step of the process we are in. - let isLinkSent = false; - - form.addEventListener('submit', async (e) => { - e.preventDefault(); - showMessage('', 'none'); // Clear previous messages - - if (!isLinkSent) { - // --- Step 1: Request the reset link --- - const email = emailInput.value; - if (!email) { - showMessage('Please enter your email address.', 'error'); - return; - } - - try { - setLoading(true, 'Sending...'); - const response = await fetch('/api/auth/requestPasswordReset', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email }), - }); - - const data = await response.json(); - if (!response.ok) throw new Error(data.message); - - // Transition to the next step - showMessage('A reset link has been sent to your email. Please copy the ID and Token from the link into the fields below.', 'success'); - isLinkSent = true; - emailInput.readOnly = true; // Lock the email field - resetFields.style.display = 'block'; - subtitle.textContent = 'Enter the details from your reset link.'; - setLoading(false, 'Reset Password'); - - } catch (error) { - showMessage(error.message, 'error'); - setLoading(false, 'Send Reset Link'); - } - } else { - // --- Step 2: Submit the ID, token, and new password --- - const id = userIdInput.value; - const token = tokenInput.value; - const password = passwordInput.value; - - if (!id || !token || !password) { - showMessage('Please fill in your User ID, Token, and a new password.', 'error'); - return; - } - - try { - setLoading(true, 'Resetting...'); - // The URL is constructed based on your authRoutes.js - const response = await fetch(`/api/auth/resetpassword/${id}/${token}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ password }), - }); - - const data = await response.json(); - if (!response.ok) throw new Error(data.message); - - showMessage('Password reset successfully! Redirecting to login...', 'success'); - - // Redirect to login page after a short delay - setTimeout(() => { - window.location.href = '/login.html'; - }, 2000); - - } catch (error) { - showMessage(error.message, 'error'); - setLoading(false, 'Reset Password'); - } - } - }); - - function setLoading(isLoading, buttonText) { - submitButton.disabled = isLoading; - submitButton.textContent = buttonText; - } - - function showMessage(text, type) { - messageArea.textContent = text; - // Add CSS classes for styling success/error messages - messageArea.className = 'message'; - if (type === 'error') { - messageArea.classList.add('error'); - } else if (type === 'success') { - messageArea.classList.add('success'); - } - } -}); \ No newline at end of file diff --git a/public/scripts/index.js b/public/scripts/index.js deleted file mode 100644 index 18e9d37..0000000 --- a/public/scripts/index.js +++ /dev/null @@ -1,97 +0,0 @@ -const loginBtns = document.querySelectorAll('.login'); -const signupBtns = document.querySelectorAll('.signup'); - -const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); -const mainNav = document.querySelector('.main-nav'); -const navLinks = document.querySelector('.nav-links'); - - -const accessToken = localStorage.getItem('accessToken'); -const role = localStorage.getItem('role'); - -const headerButtonsDiv = document.querySelector('.header-buttons'); -const heroBtnsDiv = document.querySelector('.hero-btns'); - -loginBtns.forEach(btn => { - btn.addEventListener('click', ()=> { - window.location.href = 'login.html' - }) -}) - - -signupBtns.forEach(btn => { - btn.addEventListener('click', ()=> { - window.location.href = 'signup.html' - }) -}) -mobileMenuBtn.addEventListener('click', () => { - mainNav.classList.toggle('is-active'); - }) - -// Only try to fetch user data if a token exists -if (accessToken && role) { - // Use relative paths for API calls to work in any environment - const endpoint = role === 'Admin' ? '/api/auth/me/admin' : '/api/auth/me/passenger'; - - fetch(endpoint, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${accessToken}` - } - }) - .then(res => { - if (!res.ok) { - // If token is invalid/expired, clear storage - throw new Error('Invalid token'); - } - return res.json(); - }) - .then(data => { - if (data && data.id) { - console.log('Logged in as:', role); - headerButtonsDiv.innerHTML = ` - - `; - - // Add logout button to mobile nav - const logoutLi = document.createElement('li'); - const logoutMobileBtn = document.createElement('a'); - logoutMobileBtn.href = '#'; - logoutMobileBtn.textContent = 'Logout'; - logoutMobileBtn.classList.add('logout'); - logoutLi.appendChild(logoutMobileBtn); - navLinks.appendChild(logoutLi); - - - const logoutBtn = document.querySelector('.logout'); - logoutBtn.addEventListener('click', () => { - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - alert('Logged out successfully'); - window.location.href = 'index.html'; - }); - - // Add event listener for the new mobile logout button - logoutMobileBtn.addEventListener('click', (e) => { - e.preventDefault(); - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = 'index.html'; - }); - heroBtnsDiv.innerHTML = ` - - `; - const dashboardBtn = document.querySelector('.dashboard'); - dashboardBtn.addEventListener('click', () => { - window.location.href = role === 'Admin' ? 'admin-dashboard.html' : 'select-stage.html'; - }); - } - }) - .catch(() => { - // If fetch fails for any reason (e.g., invalid token), clear localStorage - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - }); -} \ No newline at end of file diff --git a/public/scripts/login.js b/public/scripts/login.js deleted file mode 100644 index 6e4ef36..0000000 --- a/public/scripts/login.js +++ /dev/null @@ -1,137 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const form = document.querySelector('.form'); - const emailInput = document.getElementById('email-address'); - const passwordInput = document.getElementById('password'); - const tokenInput = document.getElementById('2fa-token'); - const twoFaContainer = document.getElementById('2fa-container'); - const messageArea = document.getElementById('message-area'); - const submitButton = document.getElementById('submit-button'); - const subtitle = document.getElementById('subtitle-text'); - const googleButton = document.getElementById('google-signin-button'); - - let tempToken = null; // To store the temporary token for 2FA - - // Check for authentication errors from URL parameters (e.g., from Google OAuth) - const params = new URLSearchParams(window.location.search); - if (params.get('error') === 'auth_failed') { - showMessage('Authentication failed. Please try again.', 'error'); - // Clean up the URL - window.history.replaceState({}, document.title, "/login.html"); - } - - form.addEventListener('submit', async (e) => { - e.preventDefault(); - clearMessage(); - - try { - // If tempToken is present, we are in the second step (2FA verification) - if (tempToken) { - await verifyTwoFactor(); - } else { - // Otherwise, this is the first step (email/password login) - await loginWithPassword(); - } - } catch (error) { - console.error('An unexpected error occurred:', error); - showMessage('An unexpected error occurred. Please try again.', 'error'); - } - }); - - googleButton.addEventListener('click', () => { - // Redirect the user to the backend route that starts the Google OAuth flow - window.location.href = '/api/auth/google'; - }); - - async function loginWithPassword() { - const email = emailInput.value; - const password = passwordInput.value; - - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, password }), - }); - - const data = await response.json(); - - if (response.ok) { - if (data.twoFactorRequired) { - // 2FA is required, move to the second step - tempToken = data.tempToken; - prepareTwoFactorUI(); - } else { - // Login successful, no 2FA - localStorage.setItem('accessToken', data.accessToken); - localStorage.setItem('role', data.role); - redirectToDashboard(data.role); - } - } else { - // Login failed (e.g., wrong password) - showMessage(data.message || 'Login failed.', 'error'); - } - } - - async function verifyTwoFactor() { - const token = tokenInput.value; - - if (!token || token.length !== 6) { - showMessage('Please enter a valid 6-digit code.', 'error'); - return; - } - - const response = await fetch('/api/auth/2fa/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ tempToken, token }), - }); - - const data = await response.json(); - - if (response.ok) { - // 2FA verification successful - localStorage.setItem('accessToken', data.accessToken); - localStorage.setItem('role', data.role); - redirectToDashboard(data.role); - } else { - // 2FA verification failed - showMessage(data.message || 'Invalid 2FA code.', 'error'); - tokenInput.value = ''; // Clear the input for retry - tokenInput.focus(); - } - } - - function redirectToDashboard(role) { - if (role === 'Admin') { - window.location.href = '/admin-dashboard.html'; - } else if (role === 'MatAdmin') { - window.location.href = '/mat-admin-dashboard.html'; - } else { - window.location.href = '/select-stage.html'; - } - } - - function prepareTwoFactorUI() { - // Hide email and password fields - emailInput.parentElement.style.display = 'none'; - passwordInput.parentElement.style.display = 'none'; - - // Show the 2FA token input - twoFaContainer.style.display = 'block'; - tokenInput.focus(); - - // Update UI text - subtitle.textContent = 'Enter the 6-digit code from your authenticator app.'; - submitButton.textContent = 'Verify Code'; - } - - function showMessage(message, type = 'info') { - messageArea.textContent = message; - messageArea.className = `message ${type}`; // Assumes you have .error, .success classes in your CSS - messageArea.style.display = 'block'; - } - - function clearMessage() { - messageArea.textContent = ''; - messageArea.style.display = 'none'; - } -}); \ No newline at end of file diff --git a/public/scripts/manage-stages.js b/public/scripts/manage-stages.js deleted file mode 100644 index 8cb4c50..0000000 --- a/public/scripts/manage-stages.js +++ /dev/null @@ -1,345 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // --- DOM Elements --- - const stageList = document.getElementById('stage-list'); - const detailsTitle = document.getElementById('details-title'); - const detailsCoords = document.getElementById('details-coords'); - const assignedVehiclesList = document.getElementById('assigned-vehicles-list'); - const assignVehicleSelect = document.getElementById('assign-vehicle-select'); - const assignVehicleBtn = document.getElementById('assign-vehicle-btn'); - const detailsFooter = document.getElementById('details-footer'); - - // Header elements - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const userMenu = document.querySelector('.user-menu'); - const logoutLinkDesktop = document.getElementById('logout-link-desktop'); - - // Stage Modal Elements - const addStageBtn = document.getElementById('add-stage-btn'); - const stageModal = document.getElementById('add-stage-modal'); - const closeStageModalBtn = document.getElementById('close-modal-btn'); - const cancelStageBtn = document.getElementById('cancel-btn'); - const addStageForm = document.getElementById('add-stage-form'); - - // Vehicle Modal Elements - const addVehicleBtn = document.getElementById('add-vehicle-btn'); - const vehicleModal = document.getElementById('add-vehicle-modal'); - const closeVehicleModalBtn = document.getElementById('close-vehicle-modal-btn'); - const cancelVehicleBtn = document.getElementById('cancel-vehicle-btn'); - const addVehicleForm = document.getElementById('add-vehicle-form'); - const vehicleOwnerSelect = document.getElementById('vehicle-owner-select'); - - document.querySelector('.avatar').src = '/images/pfp.jpg'; - - // --- State --- - let stages = []; - let vehicles = []; - let selectedStage = null; - let matAdmins = []; - const accessToken = localStorage.getItem('accessToken'); - - // --- API Helper --- - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - return response.json(); - }; - - // --- Render Functions --- - const renderStageList = () => { - stageList.innerHTML = ''; - if (stages.length === 0) { - stageList.innerHTML = '

No stages found. Add one to get started.

'; - return; - } - stages.forEach(stage => { - const li = document.createElement('li'); - li.className = 'stage-list-item'; - li.dataset.stageId = stage.id; - li.innerHTML = ` -
flag
-

${stage.name}

- chevron_right - `; - li.addEventListener('click', () => handleStageSelect(stage.id)); - stageList.appendChild(li); - }); - }; - - const renderStageDetails = () => { - if (!selectedStage) { - detailsTitle.textContent = 'Select a Stage'; - detailsCoords.textContent = 'Details will appear here.'; - assignedVehiclesList.innerHTML = '

Select a stage to see assigned vehicles.

'; - detailsFooter.classList.add('hidden'); - return; - } - - detailsFooter.classList.remove('hidden'); - detailsTitle.textContent = selectedStage.name; - detailsCoords.innerHTML = `location_on Lat: ${selectedStage.latitude}, Long: ${selectedStage.longitude}`; - - // Highlight selected stage in the list - document.querySelectorAll('.stage-list-item').forEach(item => { - item.classList.toggle('active', item.dataset.stageId == selectedStage.id); - if (item.dataset.stageId == selectedStage.id) item.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - }); - - // Render assigned vehicles - assignedVehiclesList.innerHTML = ''; - if (selectedStage.Vehicles.length === 0) { - assignedVehiclesList.innerHTML = '

No vehicles assigned to this stage.

'; - } else { - selectedStage.Vehicles.forEach(vehicle => { - const li = document.createElement('li'); - li.className = 'vehicle-list-item'; - li.innerHTML = ` -
- local_shipping -

${vehicle.plateNumber}

-
-
- - -
- `; - li.querySelector('.unassign-btn').addEventListener('click', () => handleRemoveVehicle(vehicle.id)); - li.querySelector('.delete-btn').addEventListener('click', () => handleDeleteVehicle(vehicle.id)); - assignedVehiclesList.appendChild(li); - }); - } - - // Render vehicle assignment dropdown - const assignedVehicleIds = new Set(selectedStage.Vehicles.map(v => v.id)); - const availableVehicles = vehicles.filter(v => !assignedVehicleIds.has(v.id)); - assignVehicleSelect.innerHTML = ''; - availableVehicles.forEach(v => { - assignVehicleSelect.innerHTML += ``; - }); - }; - - // --- Event Handlers --- - const handleStageSelect = (stageId) => { - selectedStage = stages.find(s => s.id === stageId); - renderStageDetails(); - }; - - const handleAddStage = async (event) => { - event.preventDefault(); - const name = document.getElementById('stage-name-input').value; - const latitude = document.getElementById('latitude-input').value; - const longitude = document.getElementById('longitude-input').value; - - try { - const newStage = await apiFetch('/api/admin/stages', { - method: 'POST', - body: JSON.stringify({ name, latitude, longitude }), - }); - stages.push({ ...newStage, Vehicles: [] }); // Add to local state - stages.sort((a, b) => a.name.localeCompare(b.name)); // Keep it sorted - renderStageList(); - closeStageModal(); - } catch (error) { - alert(`Error creating stage: ${error.message}`); - } - }; - - const handleAssignVehicle = async () => { - const vehicleId = assignVehicleSelect.value; - if (!vehicleId || !selectedStage) return; - - try { - await apiFetch('/api/admin/stages/assign-vehicle', { - method: 'POST', - body: JSON.stringify({ stageId: selectedStage.id, vehicleId: parseInt(vehicleId) }), - }); - - // Refresh data to reflect the change - const vehicleToAdd = vehicles.find(v => v.id == vehicleId); - selectedStage.Vehicles.push(vehicleToAdd); - renderStageDetails(); - } catch (error) { - alert(`Error assigning vehicle: ${error.message}`); - } - }; - - const handleRemoveVehicle = async (vehicleId) => { - if (!selectedStage) return; - - try { - await apiFetch(`/api/admin/stages/${selectedStage.id}/vehicles/${vehicleId}`, { - method: 'DELETE', - }); - - // Refresh data to reflect the change - selectedStage.Vehicles = selectedStage.Vehicles.filter(v => v.id !== vehicleId); - renderStageDetails(); - } catch (error) { - alert(`Error removing vehicle: ${error.message}`); - } - }; - - const handleDeleteVehicle = async (vehicleId) => { - const vehicle = vehicles.find(v => v.id === vehicleId); - if (!vehicle) return; - - if (!confirm(`Are you sure you want to permanently delete vehicle ${vehicle.plateNumber}? This action cannot be undone.`)) { - return; - } - - try { - await apiFetch(`/api/admin/vehicles/${vehicleId}`, { - method: 'DELETE', - }); - // Refetch all data to ensure UI consistency - await initializeApp(true); - } catch (error) { - alert(`Error deleting vehicle: ${error.message}`); - } - }; - - const handleAddVehicle = async (event) => { - event.preventDefault(); - const plateNumber = document.getElementById('vehicle-plate-input').value; - const carId = document.getElementById('vehicle-carid-input').value; - const ownerId = vehicleOwnerSelect.value; - - try { - const newVehicle = await apiFetch('/api/admin/vehicles', { - method: 'POST', - body: JSON.stringify({ plateNumber, carId, ownerId: ownerId || null }), - }); - vehicles.push(newVehicle); - vehicles.sort((a, b) => a.plateNumber.localeCompare(b.plateNumber)); - renderStageDetails(); // Re-render dropdown with new vehicle - closeVehicleModal(); - } catch (error) { - alert(`Error creating vehicle: ${error.message}`); - } - }; - - // --- Modal Logic --- - const openStageModal = () => stageModal.classList.remove('hidden'); - const closeStageModal = () => { - stageModal.classList.add('hidden'); - addStageForm.reset(); - }; - - const openVehicleModal = () => vehicleModal.classList.remove('hidden'); - const closeVehicleModal = () => { - vehicleModal.classList.add('hidden'); - addVehicleForm.reset(); - }; - - const populateOwnerDropdown = () => { - vehicleOwnerSelect.innerHTML = ''; - matAdmins.forEach(admin => { - vehicleOwnerSelect.innerHTML += ``; - }); - }; - - addStageBtn.addEventListener('click', openStageModal); - closeStageModalBtn.addEventListener('click', closeStageModal); - cancelStageBtn.addEventListener('click', closeStageModal); - stageModal.addEventListener('click', (e) => { - if (e.target === stageModal) closeStageModal(); - }); - - addVehicleBtn.addEventListener('click', openVehicleModal); - closeVehicleModalBtn.addEventListener('click', closeVehicleModal); - cancelVehicleBtn.addEventListener('click', closeVehicleModal); - vehicleModal.addEventListener('click', (e) => { - if (e.target === vehicleModal) closeVehicleModal(); - }); - - const logoutUser = async () => { - try { - await apiFetch('/api/auth/logout', { method: 'POST' }); - } catch (error) { - console.error('Logout failed, but clearing session anyway.'); - } - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - }; - // --- Initialization --- - const initializeApp = async (isRefresh = false) => { - // Auth check - const role = localStorage.getItem('role'); - if (!accessToken || role !== 'Admin') { - window.location.href = '/login.html'; - return; - } - if (isRefresh) selectedStage = null; // Reset selection on refresh - - // --- Mobile Menu Toggle --- - mobileMenuBtn.addEventListener('click', () => { - mainNav.classList.toggle('is-active'); - }); - - // --- Desktop User Menu Toggle --- - if (userMenu) { - userMenu.addEventListener('click', (e) => { - e.stopPropagation(); - userMenu.classList.toggle('is-active'); - }); - } - - // --- Add Logout Links --- - if (!isRefresh) { - const navList = mainNav.querySelector('ul'); - const logoutLi = document.createElement('li'); - logoutLi.innerHTML = `Logout`; - navList.appendChild(logoutLi); - logoutLi.querySelector('.logout-link').addEventListener('click', logoutUser); - logoutLinkDesktop.addEventListener('click', logoutUser); - - // Add event listeners - addStageForm.addEventListener('submit', handleAddStage); - addVehicleForm.addEventListener('submit', handleAddVehicle); - assignVehicleBtn.addEventListener('click', handleAssignVehicle); - } - - // Initial data fetch - try { - const [stagesData, vehiclesData, matAdminsData] = await Promise.all([ - apiFetch('/api/admin/stages'), - apiFetch('/api/admin/vehicles'), - apiFetch('/api/admin/mat-admins'), - ]); - stages = stagesData; - vehicles = vehiclesData; - matAdmins = matAdminsData; - renderStageList(); - populateOwnerDropdown(); - renderStageDetails(); // Render the initial empty state for details - } catch (error) { - stageList.innerHTML = `

Failed to load data: ${error.message}

`; - } - }; - - // Close user menu when clicking outside - document.addEventListener('click', () => { - if (userMenu && userMenu.classList.contains('is-active')) { - userMenu.classList.remove('is-active'); - } - }); - - initializeApp(); -}); \ No newline at end of file diff --git a/public/scripts/manage-users.js b/public/scripts/manage-users.js deleted file mode 100644 index b280830..0000000 --- a/public/scripts/manage-users.js +++ /dev/null @@ -1,220 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const accessToken = localStorage.getItem('accessToken'); - const userAvatar = document.getElementById('user-avatar'); - const userName = document.querySelector('.user-name'); - const logoutLinkDesktop = document.getElementById('logout-link-desktop'); - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const usersTableBody = document.getElementById('users-table-body'); - const searchInput = document.getElementById('user-search-input'); - const roleFilterButtons = document.getElementById('role-filter-buttons'); - - // Modal elements - const addUserModal = document.getElementById('add-user-modal'); - const addUserBtn = document.getElementById('add-user-btn'); - const closeModalBtn = document.getElementById('close-user-modal-btn'); - const cancelBtn = document.getElementById('cancel-user-btn'); - const addUserForm = document.getElementById('add-user-form'); - const passwordToggleBtn = document.querySelector('.password-toggle-btn'); - - let searchTimeout; - - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - // Handle responses with no content - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - return response.json(); - } - return; - }; - - const logoutUser = async () => { - try { - await apiFetch('/api/auth/logout', { method: 'POST' }); - } catch (error) { - console.error('Logout failed, but clearing session anyway.'); - } - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - }; - - function createUserRow(user) { - const statusClass = user.disabled ? 'badge-danger' : 'badge-success'; - const statusText = user.disabled ? 'Blocked' : 'Active'; - const blockButtonText = user.disabled ? 'Unblock' : 'Block'; - - return ` - - ${user.name} - ${user.email} - ${user.role} - ${statusText} - - - - - - `; - } - - async function fetchAndDisplayUsers(query = '', role = '') { - try { - const params = new URLSearchParams({ q: query, role: role }); - const users = await apiFetch(`/api/admin/users?${params.toString()}`); - - if (users.length > 0) { - usersTableBody.innerHTML = users.map(createUserRow).join(''); - } else { - usersTableBody.innerHTML = 'No users found.'; - } - } catch (error) { - console.error('Error fetching users:', error); - usersTableBody.innerHTML = 'Could not load users.'; - } - } - - async function handleTableClick(event) { - const target = event.target; - const row = target.closest('tr'); - if (!row) return; - - const userId = row.dataset.userId; - - if (target.classList.contains('btn-block')) { - const shouldBeDisabled = target.dataset.disabled === 'true'; - if (confirm(`Are you sure you want to ${shouldBeDisabled ? 'block' : 'unblock'} this user?`)) { - try { - await apiFetch(`/api/admin/users/${userId}`, { - method: 'PUT', - body: JSON.stringify({ disabled: shouldBeDisabled }), - }); - fetchAndDisplayUsers(searchInput.value); // Refresh list - } catch (error) { - alert(`Error updating user: ${error.message}`); - } - } - } - - if (target.classList.contains('btn-delete')) { - if (confirm('Are you sure you want to permanently delete this user? This action cannot be undone.')) { - try { - await apiFetch(`/api/admin/users/${userId}`, { method: 'DELETE' }); - row.remove(); // Optimistically remove from UI - } catch (error) { - alert(`Error deleting user: ${error.message}`); - } - } - } - } - - async function handleAddUser(event) { - event.preventDefault(); - const name = document.getElementById('user-name-input').value; - const email = document.getElementById('user-email-input').value; - const password = document.getElementById('user-password-input').value; - const role = document.getElementById('user-role-select').value; - - try { - await apiFetch('/api/admin/users', { - method: 'POST', - body: JSON.stringify({ name, email, password, role }), - }); - closeModal(); - fetchAndDisplayUsers(); // Refresh the list - } catch (error) { - alert(`Error creating user: ${error.message}`); - } - } - - function togglePasswordVisibility() { - const passwordInput = document.getElementById('user-password-input'); - const icon = passwordToggleBtn.querySelector('.material-symbols-outlined'); - if (passwordInput.type === 'password') { - passwordInput.type = 'text'; - icon.textContent = 'visibility_off'; - passwordToggleBtn.title = 'Hide password'; - } else { - passwordInput.type = 'password'; - icon.textContent = 'visibility'; - passwordToggleBtn.title = 'Show password'; - } - } - - // Modal handling - const openModal = () => addUserModal.classList.remove('hidden'); - const closeModal = () => { - addUserModal.classList.add('hidden'); - addUserForm.reset(); - }; - - const initializeApp = async () => { - const role = localStorage.getItem('role'); - if (!accessToken || role !== 'Admin') { - window.location.href = '/login.html'; - return; - } - - try { - const user = await apiFetch('/api/auth/me/admin'); - userAvatar.src = user.profilePictureUrl || '/images/pfp.jpg'; - userName.textContent = user.name.split(' ')[0]; - } catch (e) { - console.error("Failed to fetch user profile", e); - } - - // Event Listeners - logoutLinkDesktop.addEventListener('click', logoutUser); - mobileMenuBtn.addEventListener('click', () => mainNav.classList.toggle('is-active')); - usersTableBody.addEventListener('click', handleTableClick); - addUserBtn.addEventListener('click', openModal); - closeModalBtn.addEventListener('click', closeModal); - cancelBtn.addEventListener('click', closeModal); - addUserForm.addEventListener('submit', handleAddUser); - passwordToggleBtn.addEventListener('click', togglePasswordVisibility); - - // Debounced search - searchInput.addEventListener('keyup', () => { - clearTimeout(searchTimeout); - const activeRole = roleFilterButtons.querySelector('button.active').dataset.role; - searchTimeout = setTimeout(() => { - fetchAndDisplayUsers(searchInput.value, activeRole); - }, 300); // 300ms delay - }); - - // Role filter buttons - roleFilterButtons.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - roleFilterButtons.querySelector('button.active').classList.remove('active'); - e.target.classList.add('active'); - fetchAndDisplayUsers(searchInput.value, e.target.dataset.role); - } - }); - - // Add mobile logout link - const navList = mainNav.querySelector('ul'); - const logoutLi = document.createElement('li'); - logoutLi.innerHTML = `Logout`; - navList.appendChild(logoutLi); - logoutLi.querySelector('#logout-link-mobile').addEventListener('click', logoutUser); - - // Initial data fetch - fetchAndDisplayUsers(); - }; - - initializeApp(); -}); \ No newline at end of file diff --git a/public/scripts/mat-admin-dashboard.js b/public/scripts/mat-admin-dashboard.js deleted file mode 100644 index f298e45..0000000 --- a/public/scripts/mat-admin-dashboard.js +++ /dev/null @@ -1,119 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const accessToken = localStorage.getItem('accessToken'); - const userAvatar = document.getElementById('user-avatar'); - const userName = document.querySelector('.user-name'); - const logoutLinkDesktop = document.getElementById('logout-link-desktop'); - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - - // Stat card elements - const totalVehiclesStat = document.getElementById('total-vehicles-stat'); - const totalDistanceStat = document.getElementById('total-distance-stat'); - const totalTripsStat = document.getElementById('total-trips-stat'); - const vehicleTableBody = document.getElementById('vehicle-table-body'); - - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - return response.json(); - }; - - const logoutUser = async () => { - try { - await apiFetch('/api/auth/logout', { method: 'POST' }); - } catch (error) { - console.error('Logout failed, but clearing session anyway.'); - } - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - }; - - async function fetchDashboardSummary() { - try { - const summary = await apiFetch('/api/mat-admin/dashboard-summary'); - totalVehiclesStat.textContent = summary.totalVehicles; - totalDistanceStat.textContent = `${(summary.totalDistanceToday / 1000).toFixed(2)} km`; - totalTripsStat.textContent = summary.totalTripsToday; - } catch (error) { - console.error('Error fetching summary:', error); - totalVehiclesStat.textContent = 'Error'; - } - } - - function createVehicleRow(vehicle) { - console.log(vehicle); - const statusClass = vehicle.status.toLowerCase() === 'online' ? 'badge-success' : 'badge-danger'; - let locationCell = vehicle.location || 'N/A'; - - if (vehicle.latitude && vehicle.longitude) { - const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${vehicle.latitude},${vehicle.longitude}`; - locationCell = `${vehicle.location}`; - } - - return ` - - ${vehicle.plateNumber} - ${vehicle.status} - ${locationCell} - - `; - } - - async function fetchLiveVehicleStatus() { - try { - const data = await apiFetch('/api/mat-admin/dashboard-data'); - if (data.vehicles && data.vehicles.length > 0) { - console.log(data); - - vehicleTableBody.innerHTML = data.vehicles.map(createVehicleRow).join(''); - } else { - vehicleTableBody.innerHTML = 'No vehicles found.'; - } - } catch (error) { - console.error('Error fetching live vehicle data:', error); - vehicleTableBody.innerHTML = 'Could not load vehicle data.'; - } - } - - const initializeApp = async () => { - const role = localStorage.getItem('role'); - if (!accessToken || role !== 'MatAdmin') { - window.location.href = '/login.html'; - return; - } - - const user = await apiFetch('/api/auth/me/mat-admin'); - userAvatar.src = user.profilePictureUrl || '/images/pfp.jpg'; - userName.textContent = user.name.split(' ')[0]; - - logoutLinkDesktop.addEventListener('click', logoutUser); - mobileMenuBtn.addEventListener('click', () => mainNav.classList.toggle('is-active')); - - // Add mobile logout link - const navList = mainNav.querySelector('ul'); - const logoutLi = document.createElement('li'); - logoutLi.innerHTML = `Logout`; - navList.appendChild(logoutLi); - logoutLi.querySelector('#logout-link-mobile').addEventListener('click', logoutUser); - - // Fetch data - fetchDashboardSummary(); - fetchLiveVehicleStatus(); - setInterval(fetchLiveVehicleStatus, 30000); // Refresh live status every 30s - }; - - initializeApp(); -}); \ No newline at end of file diff --git a/public/scripts/mat-admin-vehicles.js b/public/scripts/mat-admin-vehicles.js deleted file mode 100644 index 9a6b8d3..0000000 --- a/public/scripts/mat-admin-vehicles.js +++ /dev/null @@ -1,294 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const accessToken = localStorage.getItem('accessToken'); - const userAvatar = document.getElementById('user-avatar'); - const userName = document.querySelector('.user-name'); - const logoutLinkDesktop = document.getElementById('logout-link-desktop'); - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const statsContainer = document.getElementById('vehicle-stats-container'); - const exportFleetBtn = document.getElementById('export-fleet-report-btn'); - - // To keep track of Chart.js instances - const chartInstances = {}; - - const apiFetch = async (url, options = {}) => { - const defaultOptions = { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }; - const response = await fetch(url, { ...defaultOptions, ...options }); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - window.location.href = '/login.html'; - } - const errorData = await response.json(); - throw new Error(errorData.message || 'API request failed'); - } - return response.json(); - }; - - const logoutUser = async () => { - try { - await apiFetch('/api/auth/logout', { method: 'POST' }); - } catch (error) { - console.error('Logout failed, but clearing session anyway.'); - } - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - }; - - function createVehicleStatCard(vehicle) { - const distance = vehicle.DailyDistances[0] ? (vehicle.DailyDistances[0].distanceCovered / 1000).toFixed(2) : '0.00'; - const trips = vehicle.DailyTrips[0] ? vehicle.DailyTrips[0].tripCount : 0; - - const feedbackHtml = vehicle.Feedbacks.length > 0 - ? vehicle.Feedbacks.map(fb => ` - - `).join('') - : '

No feedback for this vehicle yet.

'; - - return ` -
-
-

${vehicle.plateNumber}

- ID: ${vehicle.carId} -
-
-
-

Distance Today

-

${distance} km

-
-
-

Trips Today

-

${trips}

-
-
- -
-

Performance Charts

-
-
- - - -
- -
-
- -
-
-
- `; - } - - async function fetchAndDisplayVehicleStats() { - try { - statsContainer.innerHTML = '

Loading vehicle stats...

'; - const vehicles = await apiFetch('/api/mat-admin/vehicle-stats'); - - if (vehicles && vehicles.length > 0) { - statsContainer.innerHTML = vehicles.map(createVehicleStatCard).join(''); - // After rendering, initialize charts and add event listeners - vehicles.forEach(vehicle => { - initializeChartForVehicle(vehicle.id); - }); - } else { - statsContainer.innerHTML = '

You have not added any vehicles yet.

'; - } - } catch (error) { - console.error('Error fetching vehicle stats:', error); - statsContainer.innerHTML = '

Could not load vehicle statistics.

'; - } - } - - function initializeChartForVehicle(vehicleId) { - const chartControls = document.querySelector(`.chart-controls[data-vehicle-id="${vehicleId}"]`); - const periodFilters = chartControls.querySelector('.chart-period-filters'); - const exportBtn = chartControls.querySelector('.export-chart-btn'); - - periodFilters.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - const period = e.target.dataset.period; - // Update active button - periodFilters.querySelector('button.active').classList.remove('active'); - e.target.classList.add('active'); - // Fetch new data and update chart - updateChart(vehicleId, period); - } - }); - - exportBtn.addEventListener('click', () => { - const chart = chartInstances[vehicleId]; - if (chart) { - const a = document.createElement('a'); - a.href = chart.toBase64Image(); - a.download = `chart_vehicle_${vehicleId}_${new Date().toISOString().slice(0, 10)}.png`; - a.click(); - } - }); - - // Initial chart load (daily) - updateChart(vehicleId, 'daily'); - } - - async function updateChart(vehicleId, period) { - try { - const data = await apiFetch(`/api/mat-admin/vehicles/${vehicleId}/chart-data?period=${period}`); - renderChart(vehicleId, data, period); - } catch (error) { - console.error(`Error fetching chart data for vehicle ${vehicleId}:`, error); - const canvas = document.getElementById(`chart-${vehicleId}`); - const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillText('Could not load chart data.', 10, 50); - } - } - - function renderChart(vehicleId, data, period) { - const ctx = document.getElementById(`chart-${vehicleId}`).getContext('2d'); - - // Destroy previous chart instance if it exists - if (chartInstances[vehicleId]) { - chartInstances[vehicleId].destroy(); - } - - const labels = data.distance.map(d => d.period); - - chartInstances[vehicleId] = new Chart(ctx, { - type: 'bar', - data: { - labels: labels, - datasets: [ - { - label: 'Distance (km)', - data: data.distance.map(d => d.total), - backgroundColor: 'rgba(74, 108, 247, 0.6)', // Primary color with opacity - borderColor: 'rgba(74, 108, 247, 1)', - borderWidth: 1, - yAxisID: 'yDistance', - }, - { - label: 'Trips', - data: data.trips.map(d => d.total), - backgroundColor: 'rgba(56, 161, 105, 0.6)', // Green color with opacity - borderColor: 'rgba(56, 161, 105, 1)', - borderWidth: 1, - yAxisID: 'yTrips', - } - ] - }, - options: { - scales: { - yDistance: { - type: 'linear', - display: true, - position: 'left', - title: { display: true, text: 'Distance (km)' } - }, - yTrips: { - type: 'linear', - display: true, - position: 'right', - title: { display: true, text: 'Trips' }, - grid: { drawOnChartArea: false } // only show the grid for the left axis - } - } - } - }); - } - - function generateCSV(data) { - const headers = ['Period', 'Total Distance (km)', 'Total Trips']; - const rows = Object.entries(data).map(([period, values]) => { - const distance = values.distance || 0; - const trips = values.trips || 0; - return [period, distance, trips]; - }); - - // Sort rows by period - rows.sort((a, b) => a[0].localeCompare(b[0])); - - let csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n"; - csvContent += rows.map(row => row.join(",")).join("\n"); - - return encodeURI(csvContent); - } - - async function handleExportFleetReport() { - const filters = document.getElementById('fleet-report-filters'); - const period = filters.querySelector('button.active').dataset.period; - - try { - const data = await apiFetch(`/api/mat-admin/fleet-aggregate-data?period=${period}`); - if (Object.keys(data).length === 0) { - alert('No data available to export for the selected period.'); - return; - } - - const csvUri = generateCSV(data); - const link = document.createElement("a"); - link.setAttribute("href", csvUri); - link.setAttribute("download", `fleet_report_${period}_${new Date().toISOString().slice(0, 10)}.csv`); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - } catch (error) { - console.error('Error exporting fleet report:', error); - alert('Failed to generate the report. Please try again.'); - } - } - - document.getElementById('fleet-report-filters').addEventListener('click', (e) => { - if (e.target.tagName !== 'BUTTON') return; - document.querySelector('#fleet-report-filters button.active').classList.remove('active'); - e.target.classList.add('active'); - }); - - const initializeApp = async () => { - const role = localStorage.getItem('role'); - if (!accessToken || role !== 'MatAdmin') { - window.location.href = '/login.html'; - return; - } - - try { - const user = await apiFetch('/api/auth/me/mat-admin'); - userAvatar.src = user.profilePictureUrl || '/images/pfp.jpg'; - userName.textContent = user.name.split(' ')[0]; - } catch (e) { - console.error("Failed to fetch user profile", e); - // Allow continuing, but with default avatar/name - } - - logoutLinkDesktop.addEventListener('click', logoutUser); - mobileMenuBtn.addEventListener('click', () => mainNav.classList.toggle('is-active')); - - // Add mobile logout link - const navList = mainNav.querySelector('ul'); - const logoutLi = document.createElement('li'); - logoutLi.innerHTML = `Logout`; - navList.appendChild(logoutLi); - logoutLi.querySelector('#logout-link-mobile').addEventListener('click', logoutUser); - - // Set date input to today - document.getElementById('stats-date').valueAsDate = new Date(); - - exportFleetBtn.addEventListener('click', handleExportFleetReport); - - fetchAndDisplayVehicleStats(); - }; - - initializeApp(); -}); \ No newline at end of file diff --git a/public/scripts/select-stage.js b/public/scripts/select-stage.js deleted file mode 100644 index 4f30892..0000000 --- a/public/scripts/select-stage.js +++ /dev/null @@ -1,130 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const stageGrid = document.getElementById('stage-grid'); - const accessToken = localStorage.getItem('accessToken'); - - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const mainNav = document.querySelector('.main-nav'); - const headerActions = document.querySelector('.header-actions'); - - const profileIcon = document.querySelector('.profile-icon'); - - if (!accessToken) { - window.location.href = '/login.html'; - return; - } - - // --- Mobile Menu Toggle --- - mobileMenuBtn.addEventListener('click', () => { - mainNav.classList.toggle('is-active'); - }); - - // --- Add Logout Buttons --- - addLogoutButtons(); - - // A simple function to create a stage card element - function createStageCard(stage) { - const card = document.createElement('a'); - card.className = 'stage-card'; - // Pass the stage ID in the URL to the next page - card.href = `/stage-info.html?stageId=${stage.id}`; - - // Use a placeholder image for now. You can add image URLs to your Stage model later. - const imageNumber = Math.floor(Math.random() * 6) + 1; // Cycle through 6 placeholder images - - card.innerHTML = ` -
- ${stage.name} -
-
-
-

${stage.name}

-

Lat: ${stage.latitude}, Long: ${stage.longitude}

-
- `; - return card; - } - - // Fetches stages from the API and displays them - async function fetchAndDisplayStages() { - try { - const response = await fetch('/api/stages', { - headers: { - 'Authorization': `Bearer ${accessToken}` - } - }); - - if (!response.ok) { - throw new Error('Failed to fetch stages.'); - } - - const stages = await response.json(); - stageGrid.innerHTML = ''; // Clear the "Loading..." message - - if (stages.length > 0) { - stages.forEach(stage => { - const card = createStageCard(stage); - stageGrid.appendChild(card); - }); - } else { - stageGrid.innerHTML = '

No stages are available at the moment.

'; - } - } catch (error) { - console.error('Error:', error); - stageGrid.innerHTML = '

Could not load stages. Please try again later.

'; - } - } - - // --- Logout Functionality --- - function logoutUser() { - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - } - - function addLogoutButtons() { - // 1. Add logout button to mobile navigation - const navList = mainNav.querySelector('ul'); - const logoutLi = document.createElement('li'); - const logoutMobileLink = document.createElement('a'); - logoutMobileLink.href = '#'; - logoutMobileLink.textContent = 'Logout'; - logoutMobileLink.classList.add('logout-link'); - logoutMobileLink.addEventListener('click', (e) => { - e.preventDefault(); - logoutUser(); - }); - logoutLi.appendChild(logoutMobileLink); - navList.appendChild(logoutLi); - - // 2. Add logout button for desktop view - const logoutDesktopBtn = document.createElement('button'); - logoutDesktopBtn.innerHTML = ''; - logoutDesktopBtn.title = 'Logout'; - logoutDesktopBtn.classList.add('logout-btn-desktop'); - logoutDesktopBtn.addEventListener('click', logoutUser); - headerActions.insertBefore(logoutDesktopBtn, headerActions.firstChild); - } - - async function fetchProfileImage() { - if (!profileIcon) return; - try { - const res = await fetch('/api/auth/me/passenger', { - headers: { Authorization: `Bearer ${accessToken}` } - }); - - if (!res.ok) throw new Error('Failed to fetch profile image.'); - - const { profilePictureUrl } = await res.json(); - profileIcon.src = profilePictureUrl || './images/pfp.jpg'; - profileIcon.refferrerPolicy = 'no-referrer'; - } catch (err) { - console.error('Error fetching profile image:', err); - profileIcon.src = './images/pfp.jpg'; - } - } - - // Fetch the profile image when the page loads - fetchProfileImage(); - - fetchAndDisplayStages(); -}); \ No newline at end of file diff --git a/public/scripts/signup.js b/public/scripts/signup.js deleted file mode 100644 index 049cec7..0000000 --- a/public/scripts/signup.js +++ /dev/null @@ -1,43 +0,0 @@ -const fullNameEl = document.getElementById("full-name"); -const emailAddressEl = document.getElementById("email-address"); -const passwordEl = document.getElementById("password"); -const confirmPasswordEl = document.getElementById("confirm-password"); -const signupForm = document.querySelector(".form"); - -signupForm.addEventListener("submit", async (e) => { - e.preventDefault(); - - const fullName = fullNameEl.value.trim(); - const email = emailAddressEl.value.trim(); - const password = passwordEl.value; - const confirmPassword = confirmPasswordEl.value; - - if (!fullName || !email || !password || !confirmPassword) { - alert("Please fill in all fields."); - return; - } - - if (password !== confirmPassword) { - alert("Passwords do not match."); - return; - } - - try { - const response = await fetch("http://localhost:5000/api/auth/register", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: fullName, email, password, role: "Passenger" }), - }); - - const result = await response.json(); - - if (response.ok) { - alert("Signup successful! Please log in."); - window.location.href = "../login.html"; - } else { - alert(result.message || "Signup failed."); - } - } catch (error) { - alert("An error occurred. Please try again."); - } -}); \ No newline at end of file diff --git a/public/scripts/stage-info.js b/public/scripts/stage-info.js deleted file mode 100644 index 4292c93..0000000 --- a/public/scripts/stage-info.js +++ /dev/null @@ -1,167 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const arrivalList = document.querySelector('.arrival-list'); - const avatar = document.querySelector('.avatar'); - const stageHeader = document.querySelector('.page-header h2'); - const accessToken = localStorage.getItem('accessToken'); - const logoutBtn = document.getElementById('logout-btn'); - const notificationBell = document.getElementById('notification-bell'); - const notificationIcon = notificationBell.querySelector('.material-symbols-outlined'); - const profileIcon = document.querySelector('.profile-icon'); - - avatar.addEventListener('click', () => { - window.location.href = '/user-profile.html'; - }); - - if (logoutBtn) { - logoutBtn.addEventListener('click', logoutUser); - } - if (notificationBell) { - notificationBell.addEventListener('click', toggleSubscription); - } - - // Get the stageId from the URL query parameters - const params = new URLSearchParams(window.location.search); - const stageId = params.get('stageId'); - - if (!stageId) { - alert('No stage selected. Redirecting...'); - window.location.href = '/select-stage.html'; - return; - } - - if (!accessToken || localStorage.getItem('role') !== 'Passenger') { - window.location.href = '/login.html'; - return; - } - - function createArrivalItem(vehicle) { - const statusClass = vehicle.status.toLowerCase().replace(' ', '-'); - return ` -
  • -
    -
    - directions_bus -
    -
    -

    ${vehicle.plateNumber}

    -

    ETA: ${vehicle.eta}

    -
    -
    -
    - - - ${vehicle.status} - -
    -
  • - `; - } - - async function fetchAndDisplayEtas() { - try { - const response = await fetch(`/api/eta/${stageId}`, { - headers: { - 'Authorization': `Bearer ${accessToken}` - } - }); - - if (!response.ok) { - throw new Error('Failed to fetch ETA data.'); - } - - const data = await response.json(); - stageHeader.textContent = data.stageName; - arrivalList.innerHTML = data.arrivals.map(createArrivalItem).join(''); - - } catch (error) { - console.error('Error:', error); - arrivalList.innerHTML = '
  • Could not load arrival times. Please try again later.
  • '; - } - } - - async function fetchSubscriptionStatus() { - try { - const response = await fetch(`/api/stages/${stageId}/subscription`, { - headers: { 'Authorization': `Bearer ${accessToken}` } - }); - if (!response.ok) throw new Error('Failed to get subscription status.'); - const data = await response.json(); - updateBellIcon(data.isSubscribed); - } catch (error) { - console.error('Error fetching subscription status:', error); - } - } - - function updateBellIcon(isSubscribed) { - if (isSubscribed) { - notificationIcon.textContent = 'notifications_active'; - notificationBell.classList.add('active'); - notificationBell.title = 'SMS alerts are ON for the next arrival. Click to turn off.'; - } else { - notificationIcon.textContent = 'notifications_off'; - notificationBell.classList.remove('active'); - notificationBell.title = 'SMS alerts are OFF. Click to get an alert for the next arrival.'; - } - } - - async function fetchProfileImage() { - if (!profileIcon) return; - try { - const res = await fetch('/api/auth/me/passenger', { - headers: { Authorization: `Bearer ${accessToken}` } - }); - - if (!res.ok) throw new Error('Failed to fetch profile image.'); - - const { profilePictureUrl } = await res.json(); - profileIcon.src = profilePictureUrl || './images/pfp.jpg'; - profileIcon.refferrerPolicy = 'no-referrer'; - } catch (err) { - console.error('Error fetching profile image:', err); - profileIcon.src = './images/pfp.jpg'; - } - } - - async function toggleSubscription() { - const isSubscribed = notificationBell.classList.contains('active'); - const endpoint = isSubscribed ? 'unsubscribe' : 'subscribe'; - const method = 'POST'; - - try { - notificationBell.disabled = true; // Prevent double-clicking - const response = await fetch(`/api/stages/${stageId}/${endpoint}`, { - method, - headers: { 'Authorization': `Bearer ${accessToken}` } - }); - - const result = await response.json(); - - if (!response.ok) { - throw new Error(result.message || 'Failed to update subscription.'); - } - - alert(result.message); - updateBellIcon(!isSubscribed); - - } catch (error) { - console.error('Error toggling subscription:', error); - alert(`Error: ${error.message}`); - } finally { - notificationBell.disabled = false; - } - } - - // Fetch profile image - fetchProfileImage(); - - // Fetch data immediately and then every 30 seconds - fetchAndDisplayEtas(); - fetchSubscriptionStatus(); - setInterval(fetchAndDisplayEtas, 30000); - - async function logoutUser() { - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - } -}); \ No newline at end of file diff --git a/public/scripts/user-profile.js b/public/scripts/user-profile.js deleted file mode 100644 index 3e4a619..0000000 --- a/public/scripts/user-profile.js +++ /dev/null @@ -1,223 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // --- DOM Elements --- - const profileForm = document.querySelector('.profile-details-section .form-grid'); - const passwordForm = document.querySelector('.password-reset-section .form-grid'); - - // 2FA Elements - const twoFaStatus = document.getElementById('2fa-status'); - const twoFaToggleBtn = document.getElementById('2fa-toggle-btn'); - const twoFaModal = document.getElementById('2fa-modal'); - const twoFaCloseModalBtn = document.getElementById('2fa-close-modal-btn'); - const twoFaCancelBtn = document.getElementById('2fa-cancel-btn'); - const twoFaVerifyBtn = document.getElementById('2fa-verify-btn'); - const qrCodeContainer = document.getElementById('qr-code-container'); - const twoFaTokenInput = document.getElementById('2fa-verify-token'); - - // Section Elements - const twoFaSection = document.getElementById('2fa-section'); - const passwordResetSection = document.getElementById('password-reset-section'); - const logoutBtn = document.getElementById('logout-btn'); - - // --- State --- - const token = localStorage.getItem('accessToken'); - - // 1. Authentication Check: Redirect if no token is found. - if (!token) { - window.location.href = '/login.html'; - return; - } - - // 2. Fetch and display user data. - fetchUserProfile(token); - - // 3. Add event listeners for the forms. - if (profileForm) { - profileForm.addEventListener('submit', (e) => handleProfileUpdate(e, token)); - } - - // Logout listener - if (logoutBtn) { - logoutBtn.addEventListener('click', logoutUser); - } - - // 2FA Listeners - if (twoFaToggleBtn) { - twoFaToggleBtn.addEventListener('click', () => handle2FaToggle(token)); - } - if (twoFaCloseModalBtn) { - twoFaCloseModalBtn.addEventListener('click', close2FaModal); - } - if (twoFaCancelBtn) { - twoFaCancelBtn.addEventListener('click', close2FaModal); - } - if (twoFaModal) { - twoFaModal.addEventListener('click', (e) => { - if (e.target === twoFaModal) close2FaModal(); - }); - } - if (twoFaVerifyBtn) { - twoFaVerifyBtn.addEventListener('click', () => handle2FaVerify(token)); - } - - /** - * Generic API fetch helper - */ - async function apiFetch(url, options) { - const response = await fetch(url, options); - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.message || 'An API error occurred'); - } - return response.json(); - }; - - /** - * Fetches user data from the server and populates the form. - * @param {string} token The JWT access token. - */ - async function fetchUserProfile(token) { - try { - const user = await apiFetch('/api/auth/me/passenger', { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - }); - populateProfileForm(user); - - } catch (error) { - console.error('Error fetching profile:', error); - alert('Could not load your profile. Please try logging in again.'); - } - } - - /** - * Fills the input fields with the user's data. - * @param {object} user The user object from the API. - */ - function populateProfileForm(user) { - document.getElementById('name').value = user.name || ''; - document.getElementById('email').value = user.email || ''; - document.getElementById('phone').value = user.phoneNumber || ''; - document.getElementById('profile-img').src = user.profilePictureUrl || './images/pfp.jpg'; - document.querySelector('.avatar').style.backgroundImage = `url(${user.profilePictureUrl || './images/pfp.jpg'})`; - - update2FaUI(user.twoFactorEnabled); - - // Conditionally hide sections for Google users - if (user.isGoogleUser) { - if (twoFaSection) twoFaSection.style.display = 'none'; - if (passwordResetSection) passwordResetSection.style.display = 'none'; - } - } - - - /** - * Handles the submission of the profile update form. - * @param {Event} event The form submission event. - * @param {string} token The JWT access token. - */ - async function handleProfileUpdate(event, token) { - event.preventDefault(); - const name = document.getElementById('name').value; - const phone = document.getElementById('phone').value; - - try { - await apiFetch('/api/auth/me', { - method: 'PUT', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, phoneNumber: phone }), - }); - - alert('Profile updated successfully!'); - } catch (error) { - console.error('Profile update error:', error); - alert(`Error: ${error.message}`); - } - } - - /** - * Handles the logic for enabling or disabling 2FA. - * @param {string} token The JWT access token. - */ - async function handle2FaToggle(token) { - const isEnabled = document.getElementById('2fa-status').classList.contains('enabled'); - - if (isEnabled) { - // --- Disable 2FA --- - if (confirm('Are you sure you want to disable Two-Factor Authentication?')) { - try { - await apiFetch('/api/auth/2fa/disable', { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}` }, - }); - alert('2FA has been disabled.'); - update2FaUI(false); - } catch (error) { - alert(`Error disabling 2FA: ${error.message}`); - } - } - } else { - // --- Enable 2FA --- - try { - const data = await apiFetch('/api/auth/2fa/setup', { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}` }, - }); - document.getElementById('qr-code-container').innerHTML = `QR Code for 2FA`; - document.getElementById('2fa-modal').classList.remove('hidden'); - } catch (error) { - alert(`Error setting up 2FA: ${error.message}`); - } - } - } - - /** - * Handles the verification of the 2FA token from the modal. - * @param {string} token The JWT access token. - */ - async function handle2FaVerify(token) { - const twoFaCode = document.getElementById('2fa-verify-token').value; - if (!twoFaCode || twoFaCode.length !== 6) { - alert('Please enter a valid 6-digit code.'); - return; - } - - try { - await apiFetch('/api/auth/2fa/verify', { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ token: twoFaCode }), - }); - alert('2FA enabled successfully!'); - close2FaModal(); - update2FaUI(true); - } catch (error) { - alert(`Verification failed: ${error.message}`); - } - } - - function close2FaModal() { - document.getElementById('2fa-modal').classList.add('hidden'); - document.getElementById('qr-code-container').innerHTML = ''; - document.getElementById('2fa-verify-token').value = ''; - } - - function update2FaUI(isEnabled) { - const statusEl = document.getElementById('2fa-status'); - const toggleBtn = document.getElementById('2fa-toggle-btn'); - statusEl.textContent = isEnabled ? 'Enabled' : 'Disabled'; - statusEl.className = `status-badge ${isEnabled ? 'enabled' : 'disabled'}`; - toggleBtn.textContent = isEnabled ? 'Disable 2FA' : 'Enable 2FA'; - } - - /** - * Logs the user out by clearing the token and redirecting. - */ - async function logoutUser() { - localStorage.removeItem('accessToken'); - localStorage.removeItem('role'); - window.location.href = '/login.html'; - } -}); \ No newline at end of file diff --git a/public/select-stage.html b/public/select-stage.html deleted file mode 100644 index ac0a0ba..0000000 --- a/public/select-stage.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - MatConnect - Select Stage - - - - - - - - - - - - - -
    -
    -

    Select Your Stage

    -
    - -

    Loading stages...

    -
    -
    -
    - - - - \ No newline at end of file diff --git a/public/signup.html b/public/signup.html deleted file mode 100644 index f099b73..0000000 --- a/public/signup.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - Sign Up - MatConnect - - - - - - - -
    -
    -
    -

    Create an Account

    -

    - Join MatConnect for real-time matatu updates. -

    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/public/stage-details.html b/public/stage-details.html deleted file mode 100644 index aeb2ae4..0000000 --- a/public/stage-details.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - Matatu Arrival System - - - - - - - - -
    -
    - -
    -
    -
    -
    -

    Kenyatta Avenue Stage

    -

    Real-time arrivals for your next ride.

    -
    -
    -
    -

    Upcoming Matatus

    -
    -
      -
    • -
      -
      - directions_bus -
      -
      -

      KDH 420X

      -

      ETA: 2 mins

      -
      -
      -
      - - - At Stage - -
      -
    • -
    • -
      -
      - directions_bus -
      -
      -

      KCL 112A

      -

      ETA: 5 mins

      -
      -
      -
      - - - Approaching - -
      -
    • -
    • -
      -
      - directions_bus -
      -
      -

      KDJ 899B

      -

      ETA: 8 mins

      -
      -
      -
      - - - Approaching - -
      -
    • -
    -
    -
    -

    Passenger Feedback

    -

    Help us improve your experience.

    - -
    -
    -
    -
    -
    -

    © 2024 Matatu Tracker. All rights reserved.

    -
    -
    -
    - - \ No newline at end of file diff --git a/public/stage-info.html b/public/stage-info.html deleted file mode 100644 index e653d0b..0000000 --- a/public/stage-info.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - Matatu Arrival System - - - - - - - - - - -
    - - -
    - - -
    -
    -

    Upcoming Matatus

    -
    -
      -
    • -
      -
      -

      Loading arrival times...

      -
      -
    • -
    -
    - - -
    - -
    -
    -

    © 2025 MatConnect. All rights reserved.

    -
    -
    -
    - - - \ No newline at end of file diff --git a/public/styles/admin-settings.css b/public/styles/admin-settings.css deleted file mode 100644 index 2f6cd93..0000000 --- a/public/styles/admin-settings.css +++ /dev/null @@ -1,220 +0,0 @@ -/* ------------------- */ -/* Variables & Globals */ -/* ------------------- */ -:root { - --primary: #607afb; - --bg: #f5f6f8; - --card-bg: #ffffff; - --border: rgba(0, 0, 0, 0.1); - --text-main: #000000; - --text-muted: rgba(0, 0, 0, 0.6); - --green-light: #dcfce7; - --green-dark: #166534; - --red-light: #fee2e2; - --red-dark: #991b1b; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "Public Sans", sans-serif; - background-color: var(--bg); - color: var(--text-main); -} - -a { - color: var(--primary); - text-decoration: none; -} -a:hover { - opacity: 0.8; -} - -/* Layout */ -.page-wrapper { - display: flex; - flex-direction: column; - min-height: 100vh; -} -.main-content { - flex: 1; - max-width: 896px; /* max-w-4xl */ - width: 100%; - margin: 0 auto; - padding: 2rem 1rem; -} - -/* Header */ -.site-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.75rem 2.5rem; - border-bottom: 1px solid var(--border); -} -.logo { - display: flex; - align-items: center; - gap: 1rem; -} -.logo-icon { - width: 1.75rem; - height: 1.75rem; - color: var(--primary); -} -.logo h2 { - font-size: 1.25rem; - font-weight: 700; -} -.main-nav { - display: flex; - align-items: center; - gap: 1.5rem; -} -.main-nav a { - font-size: 0.875rem; - font-weight: 500; - color: var(--text-muted); -} -.avatar { - width: 2.5rem; - height: 2.5rem; - border-radius: 50%; - background-size: cover; - background-position: center; -} - -/* Page Header */ -.page-header { - margin-bottom: 2rem; -} -.page-header h1 { - font-size: 1.875rem; - font-weight: 700; -} -.page-header p { - color: var(--text-muted); - margin-top: 0.25rem; -} - -/* CSS-Only Tabs */ -.tabs { - border-bottom: 1px solid var(--border); -} -.tabs input[type="radio"] { - display: none; /* Hide the actual radio buttons */ -} -.tabs label { - display: inline-block; - padding: 1rem 0.25rem; - margin-right: 2rem; - font-size: 0.875rem; - font-weight: 500; - color: var(--text-muted); - border-bottom: 2px solid transparent; - cursor: pointer; - transition: all 0.2s; -} -.tabs label:hover { - color: var(--text-main); - border-bottom-color: rgba(0, 0, 0, 0.2); -} -.tabs input:checked + label { - color: var(--primary); - border-bottom-color: var(--primary); -} -.tab-panel { - display: none; /* Hide all panels by default */ - padding-top: 2rem; -} -.tabs input:checked + label + .tab-panel { - display: block; /* Show only the active panel */ -} - -/* Table & Content Panel */ -.panel-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; -} -.panel-header h3 { - font-size: 1.25rem; - font-weight: 700; -} - -.btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - background-color: var(--primary); - color: white; - padding: 0.5rem 1rem; - border: none; - border-radius: 0.25rem; - font-size: 0.875rem; - font-weight: 700; - cursor: pointer; - transition: background-color 0.2s; -} -.btn .material-symbols-outlined { font-size: 1rem; } -.btn:hover { background-color: rgba(96, 122, 251, 0.8); } - -.table-wrapper { - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 0.25rem; - background-color: var(--card-bg); -} -.data-table { - width: 100%; - border-collapse: collapse; -} -.data-table thead { - background-color: rgba(0, 0, 0, 0.05); -} -.data-table th, .data-table td { - padding: 1rem 1.5rem; - text-align: left; - white-space: nowrap; -} -.data-table th { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-muted); -} -.data-table tbody tr:not(:last-child) { - border-bottom: 1px solid var(--border); -} -.data-table td { - font-size: 0.875rem; -} -.data-table td:first-child { - font-weight: 500; -} -.text-right { - text-align: right; -} - -/* Badges */ -.badge { - display: inline-flex; - align-items: center; - padding: 0.25rem 0.75rem; - font-size: 0.75rem; - font-weight: 500; - border-radius: 9999px; -} -.badge-success { - background-color: var(--green-light); - color: var(--green-dark); -} -.badge-danger { - background-color: var(--red-light); - color: var(--red-dark); -} \ No newline at end of file diff --git a/public/styles/general.css b/public/styles/general.css deleted file mode 100644 index 98729eb..0000000 --- a/public/styles/general.css +++ /dev/null @@ -1,155 +0,0 @@ -:root { - --primary: #607AFB; - --background-light: #f5f6f8; - --background-dark: #0f1323; - --foreground-light: #101922; - --foreground-dark: #f6f7f8; - --card-light: #ffffff; - --card-dark: #1b2734; - --subtle-light: #e5e7eb; - --subtle-dark: #374151; - - --white: #ffffff; - --black-transparent: rgba(0, 0, 0, 0.6); - --gray-200: #e5e7eb; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; - font-family: 'Public Sans', sans-serif; - color: var(--foreground-light); -} - -body { - background-color: var(--background-light); -} - -.header { - height: 8vh; - width: 100vw; - display: flex; - justify-content: space-between; - align-items: center; - padding-left: 2vw; - border-bottom: 1px solid var(--subtle-light); -} - -.header-logo { - font-weight: bold; - font-size: 1.6rem; -} - -.header-links-container { - width: 10vw; -} - -.header-links{ - list-style: none; - display: flex; - align-items: center; - justify-content: space-evenly; -} - -.header-links a{ - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; - color: var(--text-muted); - transition: color 0.2s; -} - -.header-links a:hover { - cursor: pointer; - color: var(--primary); -} - -.header-buttons { - width: fit-content; - height: 100%; - display: flex; - align-items: center; - justify-content: space-evenly; -} - -.primary-btn { - padding: 10px 15px; - border-radius: 8px; - color: white; - border: none; -} - -.primary-btn:hover { - transform: scale(1.1); - cursor: pointer; - transition: all ease-in-out 0.2s; -} - -.btn-2 { - background-color: var(--subtle-light); - color: var(--foreground-light); -} - -.btn-2:hover { - background-color: var(--gray-400); -} - -.btn-1 { - background-color: var(--primary); -} - -.footer { - width: 100%; - height: 8vh; - background-color: var(--card-light); - display: flex; - justify-content: space-evenly; - align-items: center; - border-top: 1px solid var(--subtle-light); -} - -.footer-section { - flex: 0.8; - height: 100%; - display: flex; - align-items: center; -} - -.footlogo { - padding-left: 2vw; - font-size: 1.5rem; - font-weight: bold; -} - -.footlinks { - display: flex; - justify-content: center; -} - -.footer-links { - list-style: none; - display: flex; - align-items: center; - justify-content: space-evenly; - width: 60%; -} - -.footer-links a { - text-decoration: none; - color: var(--gray-500); -} - -.footer-links a:hover { - cursor: pointer; - text-decoration: underline; -} - -.copyright { - justify-content: end; - padding-right: 2vw; - color: var(--gray-500); -} \ No newline at end of file diff --git a/public/styles/main.css b/public/styles/main.css deleted file mode 100644 index 805672d..0000000 --- a/public/styles/main.css +++ /dev/null @@ -1,167 +0,0 @@ -/* ------------------- */ -/* Variables & Globals */ -/* ------------------- */ -:root { - --primary: #137fec; - --primary-light: rgba(19, 127, 236, 0.1); - --primary-dark: #106ac4; - --background: #f6f7f8; - --card-bg: #ffffff; - --text-main: #111827; - --text-muted: #6b7280; - --border: #e5e7eb; - --green: #22c55e; - --red: #ef4444; - --active: #607AFB; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "Public Sans", sans-serif; - background-color: var(--background); - color: var(--text-main); - font-size: 16px; -} - -a { - color: var(--primary); - text-decoration: none; -} - -.container { - width: 100%; - max-width: 1280px; - margin: 0 auto; - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.nav-container { - display: flex; - justify-content: space-between; - align-items: center; - height: 8vh; -} - -.page-wrapper { - display: flex; - flex-direction: column; - min-height: 100vh; -} - -main { - flex-grow: 1; -} - -/* ------------------- */ -/* Header & Footer */ -/* ------------------- */ -.site-header { - position: sticky; - top: 0; - z-index: 20; - background-color: rgba(246, 247, 248, 0.8); - backdrop-filter: blur(4px); - border-bottom: 1px solid var(--border); - height: 8vh; - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-content { - display: flex; - justify-content: space-between; - align-items: center; - height: 4rem; -} - -.logo { - display: flex; - align-items: center; - gap: 0.75rem; - font-size: 1.25rem; - font-weight: 700; - color: var(--text-main); - cursor: pointer; -} - -.logo .material-symbols-outlined { - color: var(--primary); - font-size: 2rem; -} - -.main-nav { - display: none; /* Mobile first */ - gap: 1.5rem; -} - -.main-nav a { - text-decoration: none; - font-weight: 500; - color: var(--text-muted); - transition: color 0.2s; -} -.main-nav a:hover, .main-nav a.active { - color: var(--active); -} - -.header-actions { - display: flex; - align-items: center; - gap: 1rem; -} - -.avatar { - width: 2.5rem; - height: 2.5rem; - border-radius: 9999px; - background-size: cover; - background-position: center; - border: 2px solid var(--border); -} - -.site-footer { - border-top: 1px solid var(--border); - padding: 2rem 0; - text-align: center; - font-size: 0.875rem; - color: var(--text-muted); -} - - -.primary-btn { - padding: 10px 15px; - border-radius: 8px; - color: white; - border: none; -} - -.primary-btn:hover { - transform: scale(1.1); - cursor: pointer; - transition: all ease-in-out 0.2s; -} - -.btn-2 { - color: var(--foreground-light); -} - - -.btn-1 { - background-color: var(--primary); -} - -/* ------------------- */ -/* Responsive Nav */ -/* ------------------- */ -@media (min-width: 768px) { - .main-nav { - display: flex; - } -} \ No newline at end of file diff --git a/public/styles/manage-users.css b/public/styles/manage-users.css deleted file mode 100644 index 8c3f35c..0000000 --- a/public/styles/manage-users.css +++ /dev/null @@ -1,224 +0,0 @@ -:root { - --primary: #607afb; - --primary-light: #eef2ff; - --bg: #f5f6f8; - --card-bg: #ffffff; - --border-light: #e5e7eb; - --text-light: #111827; - --text-muted-light: #6b7280; - --green-light: #dcfce7; - --green-dark: #166534; - --red-light: #fee2e2; - --red-dark: #991b1b; - --orange-light: #ffedd5; - --orange-dark: #9a3412; - --yellow: #facc15; -} - -/* Header Controls */ -.header-controls { - display: flex; - align-items: center; - gap: 1rem; -} - -.search-bar { - display: flex; - align-items: center; - background-color: var(--bg-color); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 0 0.75rem; -} - -.search-bar .material-symbols-outlined { - color: var(--text-secondary); -} - -.search-bar input { - border: none; - background: none; - padding: 0.625rem 0.5rem; - outline: none; - font-family: inherit; -} - -.filter-buttons { - display: flex; - gap: 0.5rem; - background-color: var(--text-muted-light); - padding: 0.25rem; - border-radius: 8px; - border: 1px solid var(--border-color); -} - -.filter-buttons button { - background: none; - border: none; - padding: 0.5rem 0.75rem; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s, color 0.2s; -} - -.filter-buttons button.active { - background-color: var(--primary-color); - color: white; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -/* Table Styles */ -.actions-cell { - display: flex; - gap: 0.5rem; -} - -.btn-action { - padding: 0.375rem 0.75rem; - border-radius: 6px; - border: 1px solid var(--border-color); - background-color: transparent; - cursor: pointer; - font-weight: 500; - transition: background-color 0.2s; -} - -.btn-block { - color: var(--warning-color); -} - -.btn-block:hover { - background-color: #feebc8; /* orange-100 */ -} - -.btn-delete { - color: var(--danger-color); -} - -.btn-delete:hover { - background-color: #fed7d7; /* red-200 */ -} - -.badge-role { - padding: 0.25rem 0.75rem; - border-radius: 99px; - font-size: 0.8rem; - font-weight: 600; -} - -.role-admin { - background-color: #e9d8fd; /* purple-200 */ - color: #6b46c1; /* purple-700 */ -} - -.role-matadmin { - background-color: #bee3f8; /* blue-200 */ - color: #2c5282; /* blue-800 */ -} - -.role-passenger { - background-color: #c6f6d5; /* green-200 */ - color: #2f855a; /* green-700 */ -} - -.hidden { - display: none !important; -} - -/* ------------------- */ -/* Modal Styles */ -/* ------------------- */ -.modal-overlay { - position: fixed; - inset: 0; - background-color: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(4px); - display: flex; - align-items: center; - justify-content: center; - z-index: 50; -} - -.modal-content { - background-color: var(--card-bg); - border-radius: 0.75rem; - width: 100%; - max-width: 500px; - margin: 1rem; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--border-color); -} - -.modal-header h3 { - font-size: 1.125rem; - font-weight: 600; -} - -.modal-body { - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1rem; -} - -.form-group { - display: flex; - flex-direction: column; -} - -.form-group label { - font-size: 0.875rem; - font-weight: 500; - color: var(--text-secondary); - margin-bottom: 0.5rem; -} - -.form-group input, .form-group select { - width: 100%; - padding: 0.75rem 1rem; - background-color: #fff; /* Use white background to rely on border for definition */ - border: 1px solid #d1d5db; /* A slightly darker border for better contrast */ - border-radius: 0.5rem; - font-size: 0.9rem; - transition: border-color 0.2s, box-shadow 0.2s; -} - -.form-group input:focus, .form-group select:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(74, 108, 247, 0.2); -} - -.password-wrapper { - position: relative; - display: flex; - align-items: center; -} - -.password-wrapper input { - padding-right: 3rem; /* Make space for the icon */ -} - -.password-toggle-btn { - position: absolute; - right: 0.5rem; - color: var(--text-secondary); -} - -.modal-footer { - display: flex; - justify-content: flex-end; - gap: 0.75rem; - padding: 1rem 1.5rem; - background-color: var(--bg-color); - border-bottom-left-radius: 0.75rem; - border-bottom-right-radius: 0.75rem; -} \ No newline at end of file diff --git a/public/styles/mat-admin-vehicles.css b/public/styles/mat-admin-vehicles.css deleted file mode 100644 index 48109c1..0000000 --- a/public/styles/mat-admin-vehicles.css +++ /dev/null @@ -1,168 +0,0 @@ -/* Controls Bar */ -.controls-bar { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 1rem; - margin-bottom: 2rem; - padding: 1rem; - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: 12px; -} - -.date-filter { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.date-filter label { - font-weight: 500; - color: var(--text-secondary); -} - -.date-filter input[type="date"] { - padding: 0.5rem 0.75rem; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--bg-color); - font-family: inherit; -} - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.625rem 1rem; - border-radius: 0.5rem; - border: none; - font-weight: 600; - font-size: 0.875rem; - cursor: pointer; - transition: all 0.2s; -} - -.btn-primary { - background-color: var(--primary-color); - color: white; -} - -/* Vehicle Stats Container */ -#vehicle-stats-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); - gap: 1.5rem; -} - -.loading-placeholder { - grid-column: 1 / -1; /* Span full width */ - text-align: center; - padding: 3rem; - color: var(--text-secondary); -} - -/* Vehicle Card Styling */ -.vehicle-card { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.vehicle-card .card-header .vehicle-id { - font-size: 0.8rem; - color: var(--text-secondary); - font-weight: 500; -} - -.vehicle-stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; - text-align: center; -} - -.mini-stat { - background-color: var(--bg-color); - padding: 1rem; - border-radius: 8px; -} - -.mini-stat h4 { - margin: 0 0 0.5rem; - font-size: 0.875rem; - color: var(--text-secondary); - font-weight: 500; -} - -.mini-stat p { - margin: 0; - font-size: 1.5rem; - font-weight: 700; - color: var(--primary-color); -} - -.feedback-section, .charts-section { - border-top: 1px solid var(--border-color); - padding-top: 1.5rem; -} - -.feedback-section h4, .charts-section h4 { - margin: 0 0 1rem; - font-size: 1rem; - font-weight: 600; -} - -.feedback-item { - padding: 0.75rem; - border-bottom: 1px solid var(--border-color); -} -.feedback-item:last-child { border-bottom: none; } - -/* Chart Section */ -.chart-controls { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 1rem; - margin-bottom: 1.5rem; -} - -.chart-period-filters { - display: flex; - gap: 0.5rem; - background-color: var(--bg-color); - padding: 0.25rem; - border-radius: 8px; -} - -.chart-period-filters button { - background: none; - border: none; - padding: 0.5rem 0.75rem; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s, color 0.2s; -} - -.chart-period-filters button.active { - background-color: var(--primary-color); - color: white; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.btn-outline { - background-color: transparent; - border: 1px solid var(--border-color); - color: var(--text-secondary); -} - -.fleet-report-section { - display: flex; - align-items: center; - gap: 1rem; -} \ No newline at end of file diff --git a/public/styles/stage-details.css b/public/styles/stage-details.css deleted file mode 100644 index 0ed0ac6..0000000 --- a/public/styles/stage-details.css +++ /dev/null @@ -1,440 +0,0 @@ -:root { - --primary: #137fec; - --background-light: #f6f7f8; - --background-dark: #101922; - --gray-900: #111827; - --gray-800: #1f2937; - --gray-500: #6b7280; - --gray-400: #9ca3af; - --gray-200: #e5e7eb; - --white: #ffffff; - --green-100: #f0fdf4; - --green-800: #166534; - --green-900: #14532d; - --green-300: #86efac; - --green-500: #22c55e; - --blue-100: #eff6ff; - --blue-800: #1e40af; - --blue-900: #1e3a8a; - --blue-300: #93c5fd; - --blue-500: #3b82f6; -} - -body { - background-color: var(--background-light); - font-family: 'Public Sans', sans-serif; - color: var(--gray-800); -} - -.dark body { - background-color: var(--background-dark); - color: var(--gray-200); -} - -.material-symbols-outlined { - font-variation-settings: - 'FILL' 0, - 'wght' 400, - 'GRAD' 0, - 'opsz' 24; -} - -.flex { - display: flex; -} - -.flex-col { - flex-direction: column; -} - -.min-h-screen { - min-height: 100vh; -} - -header { - background-color: rgba(246, 247, 248, 0.8); - backdrop-filter: blur(4px); - position: sticky; - top: 0; - z-index: 10; - border-bottom: 1px solid rgba(19, 127, 236, 0.2); -} - -.dark header { - background-color: rgba(16, 25, 34, 0.8); - border-bottom-color: rgba(19, 127, 236, 0.3); -} - -.container { - width: 100%; - margin-left: auto; - margin-right: auto; - padding-left: 1rem; - padding-right: 1rem; -} - -@media (min-width: 640px) { - .container { - padding-left: 1.5rem; - padding-right: 1.5rem; - } -} - -@media (min-width: 1024px) { - .container { - padding-left: 2rem; - padding-right: 2rem; - } -} - -.items-center { - align-items: center; -} - -.justify-between { - justify-content: space-between; -} - -.h-16 { - height: 4rem; -} - -.gap-4 { - gap: 1rem; -} - -.text-primary { - color: var(--primary); -} - -.text-3xl { - font-size: 1.875rem; -} - -.text-xl { - font-size: 1.25rem; -} - -.font-bold { - font-weight: 700; -} - -.text-gray-900 { - color: var(--gray-900); -} - -.dark .text-gray-900 { - color: var(--white); -} - -.hidden { - display: none; -} - -@media (min-width: 640px) { - .sm-flex { - display: flex; - } -} - -.gap-6 { - gap: 1.5rem; -} - -.text-sm { - font-size: 0.875rem; -} - -.font-medium { - font-weight: 500; -} - -.hover-text-primary:hover { - color: var(--primary); -} - -.dark .hover-text-primary:hover { - color: var(--primary); -} - -.p-2 { - padding: 0.5rem; -} - -.rounded-full { - border-radius: 9999px; -} - -.hover-bg-primary-20:hover { - background-color: rgba(19, 127, 236, 0.2); -} - -.dark .hover-bg-primary-20:hover { - background-color: rgba(19, 127, 236, 0.3); -} - -.h-10 { - height: 2.5rem; -} - -.w-10 { - width: 2.5rem; -} - -.object-cover { - object-fit: cover; -} - -.flex-grow { - flex-grow: 1; -} - -.py-8 { - padding-top: 2rem; - padding-bottom: 2rem; -} - -.max-w-4xl { - max-width: 64rem; -} - -.mx-auto { - margin-left: auto; - margin-right: auto; -} - -.mb-8 { - margin-bottom: 2rem; -} - -.text-4xl { - font-size: 2.25rem; -} - -.text-gray-500 { - color: var(--gray-500); -} - -.dark .text-gray-500 { - color: var(--gray-400); -} - -.mt-2 { - margin-top: 0.5rem; -} - -.bg-background-light { - background-color: var(--background-light); -} - -.dark .bg-background-light { - background-color: var(--background-dark); -} - -.rounded-lg { - border-radius: 0.5rem; -} - -.shadow-sm { - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); -} - -.border { - border-width: 1px; -} - -.border-primary-20 { - border-color: rgba(19, 127, 236, 0.2); -} - -.dark .border-primary-20 { - border-color: rgba(19, 127, 236, 0.3); -} - -.p-6 { - padding: 1.5rem; -} - -.border-b { - border-bottom-width: 1px; -} - -.text-lg { - font-size: 1.125rem; -} - -.font-semibold { - font-weight: 600; -} - -.divide-y>li+li { - border-top-width: 1px; -} - -.divide-primary-20 { - border-color: rgba(19, 127, 236, 0.2); -} - -.dark .divide-primary-20 { - border-color: rgba(19, 127, 236, 0.3); -} - -.p-4 { - padding: 1rem; -} - -.hover-bg-primary-10:hover { - background-color: rgba(19, 127, 236, 0.1); -} - -.dark .hover-bg-primary-10:hover { - background-color: rgba(19, 127, 236, 0.2); -} - -.transition-colors { - transition: background-color 0.2s, color 0.2s; -} - -.bg-primary-20 { - background-color: rgba(19, 127, 236, 0.2); -} - -.dark .bg-primary-20 { - background-color: rgba(19, 127, 236, 0.3); -} - -.p-3 { - padding: 0.75rem; -} - -.text-gray-800 { - color: var(--gray-800); -} - -.dark .text-gray-800 { - color: var(--gray-200); -} - -.gap-2 { - gap: 0.5rem; -} - -.inline-flex { - display: inline-flex; -} - -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} - -.py-1 { - padding-top: 0.25rem; - padding-bottom: 0.25rem; -} - -.bg-green-100 { - background-color: var(--green-100); -} - -.text-green-800 { - color: var(--green-800); -} - -.dark .bg-green-100 { - background-color: var(--green-900); -} - -.dark .text-green-800 { - color: var(--green-300); -} - -.w-2 { - width: 0.5rem; -} - -.h-2 { - height: 0.5rem; -} - -.mr-2 { - margin-right: 0.5rem; -} - -.bg-green-500 { - background-color: var(--green-500); -} - -.bg-blue-100 { - background-color: var(--blue-100); -} - -.text-blue-800 { - color: var(--blue-800); -} - -.dark .bg-blue-100 { - background-color: var(--blue-900); -} - -.dark .text-blue-800 { - color: var(--blue-300); -} - -.bg-blue-500 { - background-color: var(--blue-500); -} - -.mt-8 { - margin-top: 2rem; -} - -.text-center { - text-align: center; -} - -.mt-1 { - margin-top: 0.25rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - -.bg-primary { - background-color: var(--primary); -} - -.text-white { - color: var(--white); -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} - -.hover-bg-primary-90:hover { - background-color: rgba(19, 127, 236, 0.9); -} - -footer { - background-color: var(--background-light); - border-top: 1px solid rgba(19, 127, 236, 0.2); - margin-top: 2rem; -} - -.dark footer { - background-color: var(--background-dark); - border-top-color: rgba(19, 127, 236, 0.3); -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} \ No newline at end of file diff --git a/public/styles/user-profile.css b/public/styles/user-profile.css deleted file mode 100644 index 91d7cc9..0000000 --- a/public/styles/user-profile.css +++ /dev/null @@ -1,238 +0,0 @@ -.hidden { display: none !important; } - -.main-content { - padding-top: 3rem; - padding-bottom: 3rem; -} - -.profile-card { - max-width: 896px; - margin: 0 auto; - background-color: var(--card-bg); - border: 1px solid var(--border); - border-radius: 0.75rem; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - padding: 2rem; - overflow: hidden; -} - -.card-header h2 { - font-size: 1.5rem; - font-weight: 700; - margin-bottom: 0.5rem; -} - -.card-header p { - color: var(--text-muted); -} - -.profile-details-section { - display: flex; - flex-direction: column; - gap: 2rem; - margin-top: 2rem; - margin-bottom: 2rem; -} - -.profile-picture-container { - flex-shrink: 0; - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; -} - -.profile-picture, #profile-img { - width: 8rem; - height: 8rem; - border-radius: 50%; - object-fit: cover; -} - -.hidden { display: none; } - -.divider { - border: 0; - border-top: 1px solid var(--border); - margin: 2rem 0; -} - -.form-grid { - flex-grow: 1; - display: grid; - grid-template-columns: 1fr; - gap: 1.5rem; -} - -.form-group { - display: flex; - flex-direction: column; -} - -label { - font-size: 0.875rem; - font-weight: 500; - margin-bottom: 0.5rem; -} - -.form-input { - width: 100%; - padding: 0.75rem 1rem; - background-color: var(--background); - border: 1px solid var(--border); - border-radius: 0.5rem; -} - -.form-input:focus { - outline: 2px solid transparent; - outline-offset: 2px; - border-color: var(--primary); - box-shadow: 0 0 0 2px var(--primary-light); -} - -.form-actions { - display: flex; - justify-content: flex-end; - grid-column: 1 / -1; -} - -.password-reset-section, .two-factor-section { - padding: 1.5rem 0; -} - -.two-factor-section { - padding: 1.5rem 0; -} - -.two-factor-section h3 { - font-size: 1.25rem; - font-weight: 600; - margin-bottom: 1rem; -} - -.two-factor-content { - display: grid; - grid-template-columns: 1fr auto; - gap: 1rem; - align-items: center; -} - -.two-factor-content .description { - color: var(--text-muted); - font-size: 0.875rem; - max-width: 60ch; -} - -.two-factor-content .status-badge { - font-weight: 600; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; -} - -.status-badge.enabled { - background-color: var(--green-100); - color: var(--green-800); -} - -.status-badge.disabled { - background-color: var(--gray-200); - color: var(--gray-800); -} - -.two-factor-content .btn { - justify-self: end; -} - -.password-reset-section h3 { - font-size: 1.25rem; - font-weight: 600; - margin-bottom: 1rem; -} - -.modal-overlay { - position: fixed; - inset: 0; - background-color: rgba(17, 24, 39, 0.6); - backdrop-filter: blur(4px); - display: flex; - align-items: center; - justify-content: center; - z-index: 50; -} - -.modal-content { - background-color: white; - border-radius: 0.75rem; - width: 100%; - max-width: 500px; - margin: 1rem; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--border); -} - -.modal-header h3 { font-size: 1.125rem; font-weight: 600; } -.modal-body { padding: 1.5rem; display: flex; flex-direction: column; gap: 1.5rem; text-align: center; } -.modal-body .qr-code-container { margin: 0 auto; } -.modal-body .qr-code-container img { border: 4px solid var(--border); border-radius: 0.5rem; } - -.modal-footer { - display: flex; - justify-content: flex-end; - gap: 0.75rem; - padding: 1rem 1.5rem; - background-color: var(--bg); - border-bottom-left-radius: 0.75rem; - border-bottom-right-radius: 0.75rem; -} - -.btn { - font-weight: 700; - padding: 0.5rem 1rem; - border-radius: 0.5rem; - border: none; - cursor: pointer; - transition: background-color 0.2s; -} - -.btn-primary { - background-color: var(--primary); - color: #fff; -} - -.btn-primary:hover { - background-color: var(--primary-dark); -} - -.btn-secondary { - background-color: var(--primary-light); - color: var(--text-main); -} - -.btn-secondary:hover { - background-color: rgba(19, 127, 236, 0.2); -} - -@media (min-width: 640px) { - .form-grid { - grid-template-columns: repeat(2, 1fr); - } - - .two-factor-content { - grid-template-columns: 1fr auto; - } - .two-factor-content .btn { justify-self: end; } -} - -@media (min-width: 768px) { - .profile-details-section { - flex-direction: row; - align-items: flex-start; - } -} \ No newline at end of file diff --git a/public/user-profile.html b/public/user-profile.html deleted file mode 100644 index eceb895..0000000 --- a/public/user-profile.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - User Profile - - - - - - - - - - - -
    - -
    -
    -
    -

    Profile

    -

    Manage your personal details and preferences.

    -
    - -
    -
    -
    - User profile picture -
    - -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    - -
    - -
    -

    Two-Factor Authentication (2FA)

    -
    -
    -

    - Status: Loading... -

    -

    - Add an extra layer of security to your account by requiring a second verification step when you sign in. -

    -
    -
    -
    -
    - -
    - -
    -

    Password Reset

    -

    To change your password, please use the "Forgot Password" - link on the login page.

    - Go to Password Reset -
    -
    -
    - - - -
    - - - \ No newline at end of file diff --git a/scripts/check-migrations.mjs b/scripts/check-migrations.mjs new file mode 100644 index 0000000..318e850 --- /dev/null +++ b/scripts/check-migrations.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +// Enforces the migration rules in docs/MIGRATIONS.md. +// +// npm run db:migrate:check # local: structure + destructive-SQL checks +// MIGRATIONS_BASE_REF=origin/main npm run db:migrate:check # CI: also immutability + ordering +// +// Schema drift (schema.prisma vs migrations) is checked separately with `prisma migrate diff` +// in CI, because it needs a shadow database. + +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import { checkMigrations } from "./lib/migration-rules.mjs"; + +const MIGRATIONS = "prisma/migrations"; +const baseRef = process.env.MIGRATIONS_BASE_REF; + +const directories = readdirSync(MIGRATIONS, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + .sort(); + +const sql = Object.fromEntries( + directories.map((d) => { + const file = join(MIGRATIONS, d, "migration.sql"); + return [d, existsSync(file) ? readFileSync(file, "utf8") : null]; + }), +); +const lockPath = join(MIGRATIONS, "migration_lock.toml"); +const lockfile = existsSync(lockPath) ? readFileSync(lockPath, "utf8") : null; + +const git = (...args) => execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); + +let baseDirectories = []; +let changedFiles = []; +if (baseRef) { + try { + baseDirectories = git("ls-tree", "-d", "--name-only", `${baseRef}:${MIGRATIONS}`) + .split("\n") + .filter(Boolean); + } catch { + baseDirectories = []; // base branch has no migrations yet + } + changedFiles = git("diff", "--name-status", `${baseRef}...HEAD`, "--", MIGRATIONS) + .split("\n") + .filter(Boolean) + .map((line) => { + const [status, ...paths] = line.split("\t"); + return { status, path: paths[0] }; + }); +} + +const { errors, warnings, newMigrations } = checkMigrations({ directories, sql, lockfile, baseDirectories, changedFiles }); + +console.log(`Checked ${directories.length} migration(s)${baseRef ? ` against ${baseRef}` : ""}.`); +if (newMigrations.length && baseRef) console.log(`New in this branch: ${newMigrations.join(", ")}`); +for (const w of warnings) console.warn(`⚠️ ${w}`); +if (errors.length) { + for (const e of errors) console.error(`❌ ${e}`); + console.error(`\n${errors.length} migration rule violation(s). See docs/MIGRATIONS.md.`); + process.exit(1); +} +console.log("✅ Migration rules passed."); diff --git a/scripts/eta-worker.ts b/scripts/eta-worker.ts new file mode 100644 index 0000000..c4139aa --- /dev/null +++ b/scripts/eta-worker.ts @@ -0,0 +1,21 @@ +/** + * Standalone ETA worker for platforms that run a separate worker process (Railway, Fly, Render…) + * while the web app runs on serverless with ETA_CYCLE_MODE=cron or off. + * + * npm run eta:worker + */ +import "dotenv/config"; +import { startEtaScheduler, stopEtaScheduler } from "../src/server/jobs/scheduler"; + +startEtaScheduler(); +// Keep the process alive (the scheduler's timers are unref'd so they don't block Next.js shutdown). +const keepAlive = setInterval(() => {}, 1 << 30); + +for (const signal of ["SIGINT", "SIGTERM"] as const) { + process.on(signal, () => { + console.info(`[eta-worker] ${signal} received, stopping`); + stopEtaScheduler(); + clearInterval(keepAlive); + process.exit(0); + }); +} diff --git a/scripts/lib/migration-rules.mjs b/scripts/lib/migration-rules.mjs new file mode 100644 index 0000000..4f5b6a9 --- /dev/null +++ b/scripts/lib/migration-rules.mjs @@ -0,0 +1,108 @@ +// Pure rule checks for prisma/migrations, used by scripts/check-migrations.mjs and unit-tested in +// tests/unit/lib/migration-rules.test.ts. See docs/MIGRATIONS.md for the policy they enforce. + +/** `20261019120000_add_vehicle_colour` — 14-digit UTC timestamp, then a snake_case description. */ +export const MIGRATION_NAME = /^(\d{14})_([a-z0-9]+(?:_[a-z0-9]+)*)$/; + +/** Marker a reviewer adds to a migration to acknowledge a destructive statement on purpose. */ +export const DESTRUCTIVE_APPROVAL = /--\s*migration-safety:\s*destructive-approved\b(.*)$/im; + +/** Statements that can lose data or take long locks on a populated table. */ +export const DESTRUCTIVE_PATTERNS = [ + { id: "drop-table", pattern: /\bDROP\s+TABLE\b/i, hint: "Dropping a table deletes its data." }, + { id: "drop-column", pattern: /\bDROP\s+COLUMN\b/i, hint: "Dropping a column deletes its data — remove code usage first (expand/contract)." }, + { id: "truncate", pattern: /\bTRUNCATE\b/i, hint: "TRUNCATE deletes every row." }, + { id: "delete-without-where", pattern: /\bDELETE\s+FROM\s+"?\w+"?\s*(?:;|$)/im, hint: "DELETE without WHERE removes every row." }, + { id: "alter-type", pattern: /\bALTER\s+COLUMN\s+"?\w+"?\s+(?:SET\s+DATA\s+)?TYPE\b/i, hint: "Changing a column type rewrites the table and can fail or truncate values." }, + { id: "set-not-null", pattern: /\bALTER\s+COLUMN\s+"?\w+"?\s+SET\s+NOT\s+NULL\b/i, hint: "SET NOT NULL fails if any row is NULL — backfill in an earlier migration." }, + { id: "rename", pattern: /\bRENAME\s+(?:COLUMN|TO)\b/i, hint: "Renames break the running app version during deploy — add the new name, migrate, then drop the old one." }, + { id: "drop-enum-value", pattern: /\bDROP\s+TYPE\b/i, hint: "Dropping/recreating an enum can fail on existing rows." }, +]; + +/** Strips `--` line comments and block comments so commented-out SQL isn't flagged. */ +export function stripSqlComments(sql) { + return sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--[^\n]*/g, ""); +} + +export function findDestructiveStatements(sql) { + const code = stripSqlComments(sql); + return DESTRUCTIVE_PATTERNS.filter(({ pattern }) => pattern.test(code)).map(({ id, hint }) => ({ id, hint })); +} + +/** + * @param {object} input + * @param {string[]} input.directories migration folder names currently in prisma/migrations + * @param {Record} input.sql folder → migration.sql contents (null if missing) + * @param {string|null} input.lockfile migration_lock.toml contents + * @param {string[]} [input.baseDirectories] folders that exist on the base branch (already shipped) + * @param {{status: string, path: string}[]} [input.changedFiles] `git diff --name-status base...HEAD -- prisma/migrations` + * @returns {{ errors: string[], warnings: string[], newMigrations: string[] }} + */ +export function checkMigrations({ directories, sql, lockfile, baseDirectories = [], changedFiles = [] }) { + const errors = []; + const warnings = []; + + if (!lockfile || !/provider\s*=\s*"postgresql"/.test(lockfile)) { + errors.push('prisma/migrations/migration_lock.toml must exist and declare provider = "postgresql".'); + } + + for (const dir of directories) { + if (!MIGRATION_NAME.test(dir)) { + errors.push(`${dir}: name must look like 20261019120000_short_description (UTC timestamp + snake_case).`); + } + const content = sql[dir]; + if (content == null) errors.push(`${dir}: missing migration.sql.`); + else if (!stripSqlComments(content).trim()) errors.push(`${dir}: migration.sql is empty.`); + } + + const timestamps = directories.map((d) => d.slice(0, 14)); + const dupes = timestamps.filter((t, i) => timestamps.indexOf(t) !== i); + for (const t of new Set(dupes)) errors.push(`More than one migration uses timestamp ${t}.`); + + // Shipped migrations are immutable: the database records their checksum, and editing one makes + // `migrate deploy` fail (or silently diverge environments). + const base = new Set(baseDirectories); + for (const { status, path } of changedFiles) { + const dir = path.split("/")[2]; + if (!dir || !base.has(dir)) continue; + if (status.startsWith("M")) errors.push(`${dir}: already merged — never edit an applied migration; add a new one.`); + if (status.startsWith("D")) errors.push(`${dir}: already merged — never delete an applied migration.`); + if (status.startsWith("R")) errors.push(`${dir}: already merged — never rename an applied migration.`); + } + for (const dir of baseDirectories) { + if (!directories.includes(dir)) errors.push(`${dir}: exists on the base branch but is missing here.`); + } + + const newMigrations = directories.filter((d) => !base.has(d)).sort(); + const latestBase = [...base].sort().at(-1); + for (const dir of newMigrations) { + if (latestBase && dir.slice(0, 14) <= latestBase.slice(0, 14)) { + errors.push( + `${dir}: timestamp is not after the latest merged migration (${latestBase}). ` + + "Rebase and regenerate it with `npm run db:migrate:create` so migrations apply in order.", + ); + } + + const content = sql[dir] ?? ""; + const destructive = findDestructiveStatements(content); + if (destructive.length && !DESTRUCTIVE_APPROVAL.test(content)) { + for (const { id, hint } of destructive) { + errors.push( + `${dir}: potentially destructive statement (${id}). ${hint} If intended, add ` + + "`-- migration-safety: destructive-approved ` to the migration after review.", + ); + } + } else if (destructive.length) { + warnings.push(`${dir}: destructive statements approved (${destructive.map((d) => d.id).join(", ")}).`); + } + + if (/\bCREATE\s+(?:UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)/i.test(stripSqlComments(content)) && base.size > 0) { + warnings.push( + `${dir}: CREATE INDEX locks writes on the table while it builds. Fine for small tables; for large ones ` + + "consider CREATE INDEX CONCURRENTLY in its own migration.", + ); + } + } + + return { errors, warnings, newMigrations }; +} diff --git a/server/config/database.js b/server/config/database.js deleted file mode 100644 index 22818d9..0000000 --- a/server/config/database.js +++ /dev/null @@ -1,20 +0,0 @@ -const { Sequelize } = require("sequelize"); -require("dotenv").config(); - -const sequelize = new Sequelize( - process.env.DB_NAME, - process.env.DB_USER, - process.env.DB_PASSWORD, - { - host: process.env.DB_HOST, - dialect: "mariadb", - logging: false, - } -); - -sequelize - .authenticate() - .then(() => console.log("✅ MariaDB connected successfully")) - .catch((err) => console.error("❌ Database connection failed:", err)); - -module.exports = sequelize; \ No newline at end of file diff --git a/server/config/passport.js b/server/config/passport.js deleted file mode 100644 index 31efb69..0000000 --- a/server/config/passport.js +++ /dev/null @@ -1,56 +0,0 @@ -const passport = require("passport"); -const GoogleStrategy = require("passport-google-oauth20").Strategy; -const { User } = require("../models"); -require("dotenv").config(); - -// Construct the absolute callback URL. This is crucial for Google OAuth. -const callbackURL = `${process.env.SERVER_BASE_URL || `http://localhost:${process.env.PORT || 5000}`}/api/auth/google/callback`; - -passport.use( - new GoogleStrategy( - { - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: callbackURL, - // Pass the request object to the callback to access session info, etc. - passReqToCallback: true, - }, - async (req, accessToken, refreshToken, profile, done) => { - try { - const email = profile.emails[0].value; - let user = await User.findOne({ where: { email: email } }); - - if (user) { - // User exists. If they don't have a googleId, link the account. - if (!user.googleId) { - user.googleId = profile.id; - user.profilePictureUrl = - user.profilePictureUrl || - (profile.photos && profile.photos.length > 0 - ? profile.photos[0].value - : null); - await user.save(); - } - } else { - // User does not exist, create a new one. - user = await User.create({ - googleId: profile.id, - name: profile.displayName, - email: email, - profilePictureUrl: - profile.photos && profile.photos.length > 0 - ? profile.photos[0].value - : null, - role: "Passenger", // Default role for new Google sign-ups - }); - } - - // The 'done' callback attaches the user to req.user - return done(null, user); - } catch (err) { - console.error(" ❌ Error in Google OAuth strategy:", err); - return done(err, null); - } - }, - ), -); diff --git a/server/controllers/adminController.js b/server/controllers/adminController.js deleted file mode 100644 index 92612e7..0000000 --- a/server/controllers/adminController.js +++ /dev/null @@ -1,367 +0,0 @@ -const { Stage, Vehicle, User, Feedback } = require("../models"); -const bcrypt = require("bcrypt"); -const { Op } = require("sequelize"); -const { validationResult } = require("express-validator"); -const { getLocationName } = require("../services/googleMapsService"); -const { getLocations } = require("../services/locationCache"); - -// --- Stage Management --- - -const getAllStages = async (req, res, next) => { - try { - const rawLimit = parseInt(req.query.limit, 10); - const rawOffset = parseInt(req.query.offset, 10); - - const limit = Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : 25; - const offset = - Number.isInteger(rawOffset) && rawOffset >= 0 ? rawOffset : 0; - const { count, rows } = await Stage.findAndCountAll({ - include: { model: Vehicle, through: { attributes: [] } }, - order: [["name", "ASC"]], - limit, - offset, - }); - res.status(200).json({ stages: rows, total: count }); - } catch (error) { - next(error); - } -}; - -const createStage = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - try { - const { name, latitude, longitude } = req.body; - - const newStage = await Stage.create({ name, latitude, longitude }); - res.status(201).json(newStage); - } catch (error) { - next(error); - } -}; - -// --- Vehicle Management --- - -const getAllVehicles = async (req, res, next) => { - try { - const limit = parseInt(req.query.limit, 10) || 25; - const offset = parseInt(req.query.offset, 10) || 0; - const { count, rows } = await Vehicle.findAndCountAll({ - order: [["plateNumber", "ASC"]], - limit, - offset, - }); - res.status(200).json({ vehicles: rows, total: count }); - } catch (error) { - next(error); - } -}; - -const createVehicle = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const { carId, plateNumber, ownerId } = req.body; - const newVehicle = await Vehicle.create({ - carId, - plateNumber, - ownerId: ownerId || null, - }); - res.status(201).json(newVehicle); - } catch (error) { - next(error); - } -}; - -const deleteVehicle = async (req, res, next) => { - try { - const { id } = req.params; - const vehicle = await Vehicle.findByPk(id); - - if (!vehicle) { - return res.status(404).json({ message: "Vehicle not found" }); - } - - await vehicle.destroy(); - res.status(200).json({ message: "Vehicle deleted successfully." }); - } catch (error) { - next(error); - } -}; - -// --- Association Management --- - -const assignVehicleToStage = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const { stageId, vehicleId } = req.body; - const stage = await Stage.findByPk(stageId); - const vehicle = await Vehicle.findByPk(vehicleId); - - if (!stage || !vehicle) { - return res.status(404).json({ message: "Stage or Vehicle not found" }); - } - - await stage.addVehicle(vehicle); - res.status(200).json({ message: "Vehicle assigned to stage successfully" }); - } catch (error) { - next(error); - } -}; - -const removeVehicleFromStage = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const { stageId, vehicleId } = req.params; - const stage = await Stage.findByPk(stageId); - const vehicle = await Vehicle.findByPk(vehicleId); - if (!stage || !vehicle) - return res.status(404).json({ message: "Stage or Vehicle not found" }); - await stage.removeVehicle(vehicle); - res.status(200).json({ message: "Vehicle unassigned successfully" }); - } catch (error) { - next(error); - } -}; - -const getDashboardData = async (req, res, next) => { - try { - const vehicles = await Vehicle.findAll({ - order: [["plateNumber", "ASC"]], - include: { - model: Stage, - attributes: ["name"], - through: { attributes: [] }, // Don't include the join table attributes - }, - }); - const vehicleLocations = await getLocations(); - - const vehicleDetails = await Promise.all( - vehicles.map(async (vehicle) => { - const locationData = vehicleLocations.get(vehicle.id); - - let locationName = "Offline"; - let latitude = null; - let longitude = null; - if (locationData && locationData.latitude && locationData.longitude) { - locationName = await getLocationName(locationData); - latitude = locationData.latitude; - longitude = locationData.longitude; - } - - // Dynamically construct the route from the eager-loaded stages - let route = "Not Assigned"; - if (vehicle.Stages && vehicle.Stages.length > 0) { - // Simple route: first stage to last stage - if (vehicle.Stages.length > 1) { - // Sort stages by name to get a consistent route order, though a dedicated `order` column would be better. - const sortedStages = [...vehicle.Stages].sort((a, b) => - a.name.localeCompare(b.name), - ); - route = `${sortedStages[0].name} - ${sortedStages[sortedStages.length - 1].name}`; - } else { - route = vehicle.Stages[0].name; - } - } - - return { - id: vehicle.id, - plateNumber: vehicle.plateNumber, - status: locationData ? locationData.gpsStatus : "Offline", - location: locationName, - latitude, - longitude, - route: route, - }; - }), - ); - res.status(200).json({ vehicles: vehicleDetails }); - } catch (error) { - next(error); - } -}; - -const getAllFeedback = async (req, res, next) => { - try { - const limit = parseInt(req.query.limit, 10) || 10; - const offset = parseInt(req.query.offset, 10) || 0; - - const { count, rows } = await Feedback.findAndCountAll({ - order: [["createdAt", "DESC"]], - include: [ - { model: User, attributes: ["name"] }, - { model: Vehicle, attributes: ["plateNumber"] }, - ], - limit, - offset, - }); - res.status(200).json({ feedback: rows, total: count }); - } catch (error) { - next(error); - } -}; - -const getAllUsers = async (req, res, next) => { - try { - const { q = "", role = "" } = req.query; - const rawLimit = parseInt(req.query.limit, 10); - const rawOffset = parseInt(req.query.offset, 10); - - const limit = Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : 25; - const offset = - Number.isInteger(rawOffset) && rawOffset >= 0 ? rawOffset : 0; - - const whereClause = { - id: { [Op.ne]: req.user.id }, // Exclude the current admin - }; - - if (q) { - whereClause[Op.or] = [ - { name: { [Op.like]: `%${q}%` } }, - { email: { [Op.like]: `%${q}%` } }, - ]; - } - - if (role && ["Admin", "MatAdmin", "Passenger"].includes(role)) { - whereClause.role = role; - } - - const { count, rows } = await User.findAndCountAll({ - where: whereClause, - attributes: [ - "id", - "name", - "email", - "role", - "twoFactorEnabled", - "disabled", - ], // 'disabled' is a virtual field - order: [["name", "ASC"]], - limit: limit, - offset: offset, - }); - res.status(200).json({ users: rows, total: count }); - } catch (error) { - next(error); - } -}; - -const updateUserByAdmin = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const { id } = req.params; - const { disabled } = req.body; - - const user = await User.findByPk(id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const updatePayload = { disabled: !!disabled }; - - // If disabling the user, invalidate all their existing tokens by updating the timestamp. - if (disabled) { - updatePayload.tokensValidFrom = new Date(); - } - - await user.update(updatePayload); - res.status(200).json({ - message: `User has been ${disabled ? "blocked" : "unblocked"}.`, - }); - } catch (error) { - next(error); - } -}; - -const deleteUserByAdmin = async (req, res, next) => { - try { - const { id } = req.params; - const user = await User.findByPk(id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - await user.destroy(); - res.status(200).json({ message: "User deleted successfully." }); - } catch (error) { - next(error); - } -}; - -const getAllMatAdmins = async (req, res, next) => { - try { - const matAdmins = await User.findAll({ - where: { role: "MatAdmin" }, - attributes: ["id", "name"], // Only need id and name for the dropdown - order: [["name", "ASC"]], - }); - res.status(200).json(matAdmins); - } catch (error) { - next(error); - } -}; - -const createUserByAdmin = async (req, res, next) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const { name, email, password, role } = req.body; - - if (!["Admin", "MatAdmin"].includes(role)) { - return res.status(400).json({ message: "Invalid role specified." }); - } - - // Hash password - const hashedPassword = await bcrypt.hash(password, 10); - - // Create user - const user = await User.create({ - name, - email, - password: hashedPassword, - role, - }); - delete user.dataValues.password; - - res.status(201).json({ message: "User created successfully", user }); - } catch (error) { - next(error); - } -}; - -module.exports = { - getAllStages, - createStage, - getAllVehicles, - createVehicle, - deleteVehicle, - assignVehicleToStage, - removeVehicleFromStage, - getDashboardData, - getAllMatAdmins, - getAllFeedback, - getAllUsers, - updateUserByAdmin, - deleteUserByAdmin, - createUserByAdmin, -}; diff --git a/server/controllers/authController.js b/server/controllers/authController.js deleted file mode 100644 index 2bc3de2..0000000 --- a/server/controllers/authController.js +++ /dev/null @@ -1,659 +0,0 @@ -const bcrypt = require("bcrypt"); -const jwt = require("jsonwebtoken"); -const { User } = require("../models"); -const nodemailer = require("nodemailer"); -const speakeasy = require("speakeasy"); -const qrcode = require("qrcode"); -const { validationResult } = require("express-validator"); -const { v4: uuidv4 } = require("uuid"); -const { addToBlocklist } = require("../services/tokenBlocklistService"); - -// Login user -const loginUser = async (req, res, next) => { - // ValidationResult check is done in authRoutes.js - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const { email, password } = req.body; - const user = await User.findOne({ where: { email } }); - - if (!user) return res.status(404).json({ message: "User not found" }); - - if (user.disabled) - return res.status(403).json({ - message: - "Your account has been disabled. Please contact an administrator.", - }); - - // If user has no password, they likely registered via OAuth. - // Prevent them from logging in with a password. - if (!user.password) { - return res.status(400).json({ - message: "Please sign in using Google.", - }); - } - - const validPassword = await bcrypt.compare(password, user.password); - if (!validPassword) - return res.status(401).json({ message: "Invalid password" }); - - // Check if 2FA is enabled for the user - if (user.twoFactorEnabled) { - // If 2FA is enabled, generate a temporary token - const tempToken = jwt.sign( - { id: user.id, role: user.role }, - process.env.JWT_SECRET, - { expiresIn: "5m" }, // Short-lived token - ); - return res.status(200).json({ - twoFactorRequired: true, - tempToken: tempToken, - }); - } - - // Generate tokens - const accessToken = jwt.sign( - { - id: user.id, - role: user.role, - jti: uuidv4(), - disabled: user.disabled, // Include disabled status in token - // Include tokensValidFrom timestamp in token for quick validation without DB query - // Convert to milliseconds for consistent comparison with iat - tokensValidFrom: user.tokensValidFrom.getTime(), - }, - process.env.JWT_SECRET, - { expiresIn: "15m" }, - ); - - const refreshToken = jwt.sign( - { id: user.id, jti: uuidv4() }, // JTI for refresh token blocklisting - process.env.JWT_REFRESH_SECRET, - { expiresIn: "7d" }, - ); - - // Set refresh token in a secure, HTTP-only cookie - res.cookie("refreshToken", refreshToken, { - httpOnly: true, - secure: true, // Must be true if sameSite is 'none' - sameSite: "none", - }); - - res - .status(200) - .json({ message: "Login successful", accessToken, role: user.role }); - } catch (error) { - next(error); - } -}; - -const refreshAccessToken = (req, res, next) => { - try { - // ValidationResult check is done in authRoutes.js - const refreshToken = req.cookies.refreshToken; - if (!refreshToken) - return res.status(401).json({ message: "Refresh token missing" }); - - // CSRF Protection: Require the (potentially expired) access token in the Authorization header. - // This proves the client possesses both tokens, which a CSRF attacker cannot do. - const authHeader = req.header("Authorization"); - if (!authHeader || !authHeader.startsWith("Bearer ")) { - return res - .status(401) - .json({ message: "Access token missing for refresh" }); - } - const accessToken = authHeader.split(" ")[1]; - - // Verify the refresh token first - jwt.verify( - refreshToken, - process.env.JWT_REFRESH_SECRET, - async (err, oldDecodedRefresh) => { - if (err) - return res.status(403).json({ message: "Invalid refresh token" }); - - // Check if the refresh token has already been used/invalidated. - if ( - oldDecodedRefresh.jti && - (await isBlocklisted(oldDecodedRefresh.jti)) - ) { - return res - .status(401) - .json({ message: "Token has been invalidated" }); - } - - try { - // Verify the access token's signature (ignoring expiration) to prove it was issued by us. - const decodedAccess = jwt.verify( - accessToken, - process.env.JWT_SECRET, - { ignoreExpiration: true }, - ); - if (!decodedAccess || decodedAccess.id !== oldDecodedRefresh.id) { - return res.status(403).json({ message: "Token mismatch" }); - } - } catch (verifyError) { - // Catches malformed tokens that would otherwise crash the server. - return res - .status(401) - .json({ message: "Invalid access token for refresh" }); - } - - const user = await User.findByPk(oldDecodedRefresh.id); - // Also check if the user is disabled before issuing a new token. - if (!user || user.disabled) { - return res - .status(403) - .json({ message: "User account is disabled or not found" }); - } - // Check if tokens have been invalidated since this refresh token was issued. - if (oldDecodedRefresh.iat * 1000 < user.tokensValidFrom.getTime()) { - return res - .status(401) - .json({ message: "Token has been invalidated" }); - } - - const newAccessToken = jwt.sign( - { - id: user.id, - role: user.role, - jti: uuidv4(), - disabled: user.disabled, - tokensValidFrom: user.tokensValidFrom.getTime(), - }, - process.env.JWT_SECRET, - { expiresIn: "15m" }, - ); - - // Implement Refresh Token Rotation: issue a new refresh token. - const newRefreshToken = jwt.sign( - { id: user.id, jti: uuidv4() }, // JTI for refresh token blocklisting - process.env.JWT_REFRESH_SECRET, - { expiresIn: "7d" }, - ); - - // Invalidate the old refresh token by adding it to the blocklist. - if (oldDecodedRefresh.jti && oldDecodedRefresh.exp) { - await addToBlocklist(oldDecodedRefresh.jti, oldDecodedRefresh.exp); - } - - res.cookie("refreshToken", newRefreshToken, { - httpOnly: true, - secure: true, // Must be true if sameSite is 'none' - sameSite: "none", - }); - - res.status(200).json({ - accessToken: newAccessToken, - role: user.role, - }); - }, - ); - } catch (error) { - next(error); - } -}; - -// Create a new user -const createUser = async (req, res, next) => { - // ValidationResult check is done in authRoutes.js - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const { name, email, password } = req.body; - let role = "Passenger"; // Default role - - // --- Bootstrap Logic: Make the first user an Admin --- - const userCount = await User.count(); - if (userCount === 0) { - role = "Admin"; - console.log( - "✨ No users found. Promoting first registered user to Admin.", - ); - } - // --- End Bootstrap Logic --- - - // Hash password - const hashedPassword = await bcrypt.hash(password, 10); - - // Create user - const user = await User.create({ - name, - email, - password: hashedPassword, - role: role, - }); - delete user.dataValues.password; - - res.status(201).json({ message: "User created successfully", user }); - } catch (error) { - next(error); - } -}; - -const logoutUser = async (req, res, next) => { - try { - // Invalidate the access token by adding it to a blocklist. - const authHeader = req.header("Authorization"); - if (authHeader && authHeader.startsWith("Bearer ")) { - const token = authHeader.split(" ")[1]; - const decoded = jwt.decode(token); - if (decoded && decoded.jti && decoded.exp) { - await addToBlocklist(decoded.jti, decoded.exp); - } - } - - // Also invalidate the refresh token. - const refreshToken = req.cookies.refreshToken; - if (refreshToken) { - try { - // Verify the signature to prevent DoS attacks with forged tokens. - const decoded = jwt.verify( - refreshToken, - process.env.JWT_REFRESH_SECRET, - ); - if (decoded && decoded.jti && decoded.exp) { - await addToBlocklist(decoded.jti, decoded.exp); - } - } catch (e) { - // Ignore invalid/malformed refresh tokens during logout. - } - } - - // Clear the refresh token cookie. - res.clearCookie("refreshToken", { - httpOnly: true, - secure: true, // Must be true if sameSite is 'none' - sameSite: "none", - }); - - res.status(200).json({ message: "Logged out successfully." }); - } catch (error) { - next(error); - } -}; - -const me = async (req, res, next) => { - try { - if (!req.user) { - return res.status(401).json({ message: "Not Authenticated" }); - } - - const user = await User.findByPk(req.user.id, { - attributes: { exclude: ["password"] }, - }); - - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Convert to a plain object to add custom properties - const userObject = user.get({ plain: true }); - - // Add a flag to indicate if the user is a Google user - userObject.isGoogleUser = !!user.googleId; - // Don't send the googleId itself to the client - delete userObject.googleId; - - res.status(200).json(userObject); - } catch (error) { - next(error); - } -}; - -const updateProfile = async (req, res, next) => { - // ValidationResult check is done in authRoutes.js - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const { name, phoneNumber } = req.body; - const userId = req.user.id; - - const user = await User.findByPk(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const fieldsToUpdate = {}; - if (name) { - fieldsToUpdate.name = name; - } - - // Normalize and validate phone number if provided - if (phoneNumber !== undefined) { - // Basic validation and normalization (can be improved) - let normalizedPhone = phoneNumber.replace(/\s+/g, ""); // Remove spaces - // Example: If it starts with 0, and is not already international, assume Kenyan and prepend +254 - if (normalizedPhone.startsWith("0") && !normalizedPhone.startsWith("+")) { - normalizedPhone = `+254${normalizedPhone.substring(1)}`; // Basic assumption for Kenyan numbers - } // For full internationalization, use a library like libphonenumber-js - fieldsToUpdate.phoneNumber = normalizedPhone; - } - - await user.update(fieldsToUpdate); - - res.status(200).json({ message: "Profile updated successfully" }); - } catch (error) { - next(error); - } -}; - -const requestPasswordReset = async (req, res, next) => { - const { email } = req.body; - - try { - // Sequelize-style lookup - const user = await User.findOne({ where: { email } }); - - // To prevent user enumeration and timing attacks, and to handle OAuth-only users, - // we proceed only if a user with a password exists. Otherwise, we perform a dummy - // operation and return a generic message. - if (!user || !user.password) { - // To prevent timing attacks, perform a dummy hash operation - // that takes a similar amount of time as the JWT signing. - await bcrypt.hash(uuidv4(), 1); - return res.status(200).json({ - message: - "If an account with that email exists, a password reset link has been sent.", - }); - } - - const secret = process.env.JWT_SECRET + user.password; - const token = jwt.sign({ id: user.id, email: user.email }, secret, { - expiresIn: "1h", - }); - - // Use env vars for mail credentials. For Gmail use an App Password or OAuth2. - let transporter; - if (process.env.EMAIL_USER && process.env.EMAIL_PASS) { - transporter = nodemailer.createTransport({ - host: process.env.EMAIL_HOST || "smtp.gmail.com", - port: process.env.EMAIL_PORT ? Number(process.env.EMAIL_PORT) : 465, - secure: process.env.EMAIL_SECURE !== "false", - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASS, - }, - }); - - // verify SMTP credentials early, fall back to Ethereal on failure - try { - await transporter.verify(); - } catch (err) { - console.error( - "SMTP verify failed, falling back to Ethereal test account:", - err.message || err, - ); - const testAccount = await nodemailer.createTestAccount(); - transporter = nodemailer.createTransport({ - host: "smtp.ethereal.email", - port: 587, - secure: false, - auth: { user: testAccount.user, pass: testAccount.pass }, - }); - console.warn( - "Using Ethereal test account. Set correct SMTP env vars for production.", - ); - } - } else { - // Dev fallback - const testAccount = await nodemailer.createTestAccount(); - transporter = nodemailer.createTransport({ - host: "smtp.ethereal.email", - port: 587, - secure: false, - auth: { user: testAccount.user, pass: testAccount.pass }, - }); - console.warn( - "EMAIL_USER / EMAIL_PASS not set — using Ethereal test account for email delivery.", - ); - } - - const mailOptions = { - to: user.email, - from: - process.env.EMAIL_FROM || - process.env.EMAIL_USER || - "no-reply@example.com", - subject: "Password Reset Request", - text: `If you didn't request this, ignore this email.\n\n Reset Token: ${token}\n\n User ID: ${user.id}`, - }; - - // Send email in a separate try/catch to prevent leaking user existence on SMTP failure. - try { - const info = await transporter.sendMail(mailOptions); - // if using Ethereal, log preview URL - const preview = nodemailer.getTestMessageUrl(info); - if (preview) console.log("Preview email URL:", preview); - } catch (emailError) { - console.error("❌ Failed to send password reset email:", emailError); - // Do not throw an error to the client, just log it. - } - - res.status(200).json({ - message: - "If an account with that email exists, a password reset link has been sent.", - }); - } catch (error) { - next(error); - } -}; - -const resetPassword = async (req, res, next) => { - const { id, token } = req.params; - const { password } = req.body; - - // ValidationResult check is done in authRoutes.js - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const user = await User.findByPk(id); - if (!user) { - return res.status(400).json({ message: "User not exists!" }); - } - - const secret = process.env.JWT_SECRET + user.password; - - // verify token - jwt.verify(token, secret); - - const encryptedPassword = await bcrypt.hash(password, 10); - - // Sequelize update - await user.update({ - password: encryptedPassword, - tokensValidFrom: new Date(), - }); // Invalidate existing sessions - - res.status(200).json({ message: "Password has been reset" }); - } catch (error) { - next(error); - } -}; - -// --- Two-Factor Authentication Controllers --- - -const generateTwoFactorSecret = async (req, res, next) => { - try { - const user = await User.findByPk(req.user.id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const secret = speakeasy.generateSecret({ - name: `Realtime Matatu (${user.email})`, - }); - - // Temporarily store the un-verified secret. - // In a production app, you might want a separate field for a pending secret to not overwrite an existing enabled one. - await user.update({ twoFactorSecret: secret.base32 }); - - qrcode.toDataURL(secret.otpauth_url, (err, data_url) => { - if (err) { - console.error("Error generating QR code:", err); - return res.status(500).json({ message: "Error generating QR code" }); - } - res.json({ secret: secret.base32, qrCodeUrl: data_url }); - }); - } catch (error) { - next(error); - } -}; - -const verifyTwoFactorSecret = async (req, res, next) => { - const { token } = req.body; - try { - const user = await User.findByPk(req.user.id); - if (!user || !user.twoFactorSecret) { - return res - .status(400) - .json({ message: "2FA not set up or user not found." }); - } - - const verified = speakeasy.totp.verify({ - secret: user.twoFactorSecret, - encoding: "base32", - token: token, - }); - - if (verified) { - await user.update({ twoFactorEnabled: true }); - res.json({ message: "2FA enabled successfully" }); - } else { - res.status(400).json({ message: "Invalid 2FA token" }); - } - } catch (error) { - next(error); - } -}; - -const verifyLoginTwoFactor = async (req, res, next) => { - const { tempToken, token } = req.body; - try { - const decoded = jwt.verify(tempToken, process.env.JWT_SECRET); - const user = await User.findByPk(decoded.id); - - if (!user || !user.twoFactorEnabled || !user.twoFactorSecret) { - return res - .status(400) - .json({ message: "2FA is not enabled for this user." }); - } - - const verified = speakeasy.totp.verify({ - secret: user.twoFactorSecret, - encoding: "base32", - token: token, - window: 1, // Allow for a 30-second window of tolerance - }); - - if (verified) { - // 2FA code is correct, issue the final access token - const accessToken = jwt.sign( - { - id: user.id, - role: user.role, - jti: uuidv4(), - disabled: user.disabled, - tokensValidFrom: user.tokensValidFrom.getTime(), - }, - process.env.JWT_SECRET, - { expiresIn: "15m" }, - ); - const refreshToken = jwt.sign( - { id: user.id, jti: uuidv4() }, - process.env.JWT_REFRESH_SECRET, - { expiresIn: "7d" }, - ); // JTI for refresh token blocklisting - - res.cookie("refreshToken", refreshToken, { - httpOnly: true, - secure: true, // Must be true if sameSite is 'none' - sameSite: "none", - }); - res.json({ message: "Login successful", accessToken, role: user.role }); - } else { - res.status(400).json({ message: "Invalid 2FA token" }); - } - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - return res - .status(401) - .json({ message: "2FA session expired, please log in again." }); - } else if (error instanceof jwt.JsonWebTokenError) { - return res.status(401).json({ message: "Invalid 2FA session token." }); - } else { - // Pass to error handler for other unexpected errors. - next(error); - } - } -}; - -// This function is called after passport successfully authenticates the user. -const googleCallback = (req, res) => { - const user = req.user; - const accessToken = jwt.sign( - { - id: user.id, - role: user.role, - jti: uuidv4(), - disabled: user.disabled, - tokensValidFrom: user.tokensValidFrom.getTime(), - }, - process.env.JWT_SECRET, - { expiresIn: "15m" }, - ); - // Use a URL fragment (#) instead of a query string (?) to pass the token. - // This is more secure as fragments are not sent to the server and are less likely to be logged. - // The frontend (auth-success.html) will need to be updated to parse the token from the URL fragment. - res.redirect( - `/auth-success.html#accessToken=${accessToken}&role=${user.role}`, - ); -}; - -const disableTwoFactor = async (req, res, next) => { - try { - const user = await User.findByPk(req.user.id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // For enhanced security, you might require a password or 2FA token to disable. - // For simplicity here, we'll just disable it. - await user.update({ - twoFactorEnabled: false, - twoFactorSecret: null, // Clear the secret for security - }); - - res.json({ message: "2FA disabled successfully" }); - } catch (error) { - next(error); - } -}; - -module.exports = { - loginUser, - createUser, - me, - updateProfile, - refreshAccessToken, - logoutUser, - requestPasswordReset, - resetPassword, - generateTwoFactorSecret, - verifyTwoFactorSecret, - verifyLoginTwoFactor, - googleCallback, - disableTwoFactor, -}; diff --git a/server/controllers/etaController.js b/server/controllers/etaController.js deleted file mode 100644 index 717bf50..0000000 --- a/server/controllers/etaController.js +++ /dev/null @@ -1,31 +0,0 @@ -const { Stage, Vehicle } = require("../models"); -const { calculateEtasForStage } = require("../services/etaCalculationService"); -const { getLocations } = require("../services/locationCache"); - -const getStageEta = async (req, res, next) => { - const { stageId } = req.params; - - try { - // Find the stage by its primary key and include its associated vehicles - const stage = await Stage.findByPk(stageId, { - include: { - model: Vehicle, - through: { attributes: [] }, // Don't include the join table attributes - }, - }); - - if (!stage) { - return res.status(404).json({ message: "Stage not found" }); - } - - // Get all vehicle locations from the central cache - const allVehicleLocations = await getLocations(); - - const vehicleEtas = await calculateEtasForStage(stage, allVehicleLocations); - res.status(200).json({ stageName: stage.name, arrivals: vehicleEtas }); - } catch (error) { - next(error); - } -}; - -module.exports = { getStageEta }; diff --git a/server/controllers/feedbackController.js b/server/controllers/feedbackController.js deleted file mode 100644 index 8405f7e..0000000 --- a/server/controllers/feedbackController.js +++ /dev/null @@ -1,51 +0,0 @@ -const { Feedback, Vehicle } = require("../models"); -const { validationResult } = require("express-validator"); - -/** - * Allows a passenger to submit feedback for a specific vehicle. - */ -const submitFeedback = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - try { - const { vehicleId, rating, comment } = req.body; - const userId = req.user.id; - - // Check if vehicle exists - const vehicle = await Vehicle.findByPk(vehicleId); - if (!vehicle) { - return res.status(404).json({ message: "Vehicle not found." }); - } - - await Feedback.create({ - vehicleId, - userId, - rating, - comment, - }); - - res.status(201).json({ message: "Thank you for your feedback!" }); - } catch (error) { - next(error); - } -}; - -/** - * Fetches all vehicles so passengers can select one for feedback. - */ -const getAllVehiclesForFeedback = async (req, res, next) => { - try { - const vehicles = await Vehicle.findAll({ - attributes: ["id", "plateNumber"], - order: [["plateNumber", "ASC"]], - }); - res.status(200).json(vehicles); - } catch (error) { - next(error); - } -}; - -module.exports = { submitFeedback, getAllVehiclesForFeedback }; diff --git a/server/controllers/matAdminController.js b/server/controllers/matAdminController.js deleted file mode 100644 index 938f59b..0000000 --- a/server/controllers/matAdminController.js +++ /dev/null @@ -1,434 +0,0 @@ -const { - Vehicle, - DailyDistance, - DailyTrip, - Feedback, - Stage, // Import Stage model - User, -} = require("../models"); -const { Op, fn, col } = require("sequelize"); -const { validationResult } = require("express-validator"); -const sequelize = require("../config/database"); -const { getVehicleLocation } = require("../services/firebaseService"); -const { getLocations } = require("../services/locationCache"); -const { getLocationName } = require("../services/googleMapsService"); - -const createVehicle = async (req, res, next) => { - // ValidationResult check is done in matAdminRoutes.js - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - try { - const { carId, plateNumber } = req.body; - const ownerId = req.user.id; // Vehicle is owned by the logged-in MatAdmin - - const newVehicle = await Vehicle.create({ carId, plateNumber, ownerId }); - res.status(201).json(newVehicle); - } catch (error) { - next(error); - } -}; - -const getDashboardData = async (req, res, next) => { - try { - const ownerId = req.user.id; - // Fetch locations once before the loop to avoid N Redis/cache calls - const allVehicleLocations = await getLocations(); - - const vehicles = await Vehicle.findAll({ - where: { ownerId }, - order: [["plateNumber", "ASC"]], - include: { - attributes: ["name"], - through: { attributes: [] }, - }, - }); - - const vehicleDetails = await Promise.all( - vehicles.map(async (vehicle) => { - const locationData = allVehicleLocations.get(vehicle.id); - - let locationName = "Offline"; - let latitude = null; - let longitude = null; - if (locationData && locationData.latitude && locationData.longitude) { - locationName = await getLocationName(locationData); - latitude = locationData.latitude; - longitude = locationData.longitude; - } - - // Dynamically construct the route from associated stages - let route = "Not Assigned"; - if (vehicle.Stages && vehicle.Stages.length > 0) { - if (vehicle.Stages.length > 1) { - route = `${vehicle.Stages[0].name} - ${vehicle.Stages[vehicle.Stages.length - 1].name}`; - } else { - route = vehicle.Stages[0].name; - } - } - - return { - id: vehicle.id, - plateNumber: vehicle.plateNumber, - status: locationData ? "Online" : "Offline", - location: locationName, - latitude, - longitude, - route: route, - }; - }), - ); - res.status(200).json({ vehicles: vehicleDetails }); - } catch (error) { - next(error); - } -}; - -const getDashboardSummary = async (req, res, next) => { - try { - const ownerId = req.user.id; - const today = new Date().toISOString().slice(0, 10); - - const ownedVehicles = await Vehicle.findAll({ where: { ownerId } }); - const vehicleIds = ownedVehicles.map((v) => v.id); - - const totalVehicles = ownedVehicles.length; - - const totalDistanceResult = await DailyDistance.sum("distanceCovered", { - where: { vehicleId: { [Op.in]: vehicleIds }, date: today }, - }); - - const totalTripsResult = await DailyTrip.sum("tripCount", { - where: { vehicleId: { [Op.in]: vehicleIds }, date: today }, - }); - - res.status(200).json({ - totalVehicles, - totalDistanceToday: totalDistanceResult || 0, - totalTripsToday: totalTripsResult || 0, - }); - } catch (error) { - next(error); - } -}; - -// Helper to fetch latest feedback for a vehicle -const getLatestFeedbackForVehicle = async (vehicleId) => { - try { - const feedback = await Feedback.findAll({ - where: { vehicleId }, - include: [{ model: User, attributes: ["name"] }], - limit: 5, - order: [["createdAt", "DESC"]], - }); - return feedback; - } catch (error) { - console.error(`Error fetching feedback for vehicle ${vehicleId}:`, error); - return []; - } -}; - -const getVehicleChartData = async (req, res, next) => { - try { - const { vehicleId } = req.params; - const { period = "daily" } = req.query; // 'daily', 'weekly', 'monthly' - const ownerId = req.user.id; - - // Security check: Ensure the vehicle belongs to the logged-in MatAdmin - const vehicle = await Vehicle.findOne({ - where: { id: vehicleId, ownerId }, - }); - if (!vehicle) { - return res.status(404).json({ - message: "Vehicle not found or you do not have permission to view it.", - }); - } - - let distanceData, tripData; - const today = new Date(); - // Helper to format date to YYYY-MM-DD to avoid timezone issues with DATEONLY fields - const toYYYYMMDD = (date) => date.toISOString().slice(0, 10); - - switch (period) { - case "weekly": - const fourWeeksAgo = new Date(new Date().setDate(today.getDate() - 27)); // Go back 27 days to include today in the 4th week - // Use toYYYYMMDD to ensure correct date comparison - distanceData = await DailyDistance.findAll({ - where: { vehicleId, date: { [Op.gte]: fourWeeksAgo } }, - attributes: [ - [fn("YEARWEEK", col("date")), "period"], - [fn("SUM", col("distanceCovered")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - tripData = await DailyTrip.findAll({ - // Use toYYYYMMDD for tripData as well - where: { vehicleId, date: { [Op.gte]: fourWeeksAgo } }, - attributes: [ - [fn("YEARWEEK", col("date")), "period"], - [fn("SUM", col("tripCount")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - break; - - case "monthly": - const twelveMonthsAgo = new Date( - new Date().setMonth(today.getMonth() - 11), // Go back 11 months to include the current month - ); - distanceData = await DailyDistance.findAll({ - // Use toYYYYMMDD to ensure correct date comparison - where: { vehicleId, date: { [Op.gte]: toYYYYMMDD(twelveMonthsAgo) } }, - attributes: [ - [fn("DATE_FORMAT", col("date"), "%Y-%m"), "period"], - [fn("SUM", col("distanceCovered")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - tripData = await DailyTrip.findAll({ - // Use toYYYYMMDD for tripData as well - where: { vehicleId, date: { [Op.gte]: twelveMonthsAgo } }, - attributes: [ - [fn("DATE_FORMAT", col("date"), "%Y-%m"), "period"], - [fn("SUM", col("tripCount")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - break; - - case "daily": - default: - const sevenDaysAgo = new Date(new Date().setDate(today.getDate() - 6)); // Go back 6 days to get 7 total days - // Use toYYYYMMDD to ensure correct date comparison - distanceData = await DailyDistance.findAll({ - where: { vehicleId, date: { [Op.gte]: sevenDaysAgo } }, - attributes: [ - ["date", "period"], - ["distanceCovered", "total"], - ], - order: [["date", "ASC"]], - }); - tripData = await DailyTrip.findAll({ - // Use toYYYYMMDD for tripData as well - where: { vehicleId, date: { [Op.gte]: sevenDaysAgo } }, - attributes: [ - ["date", "period"], - ["tripCount", "total"], - ], - order: [["date", "ASC"]], - }); - break; - } - - // Convert raw data to a format Chart.js can easily use - const formatData = (data, isDistance = false) => { - return data.map((d) => ({ - period: d.get("period"), - total: isDistance - ? parseFloat((d.get("total") / 1000).toFixed(2)) - : parseInt(d.get("total"), 10), - })); - }; - - res.status(200).json({ - distance: formatData(distanceData, true), - trips: formatData(tripData, false), - }); - } catch (error) { - next(error); - } -}; - -const getFleetAggregateData = async (req, res, next) => { - try { - const { period = "daily" } = req.query; - const ownerId = req.user.id; - - const ownedVehicles = await Vehicle.findAll({ - where: { ownerId }, - attributes: ["id"], - }); - const vehicleIds = ownedVehicles.map((v) => v.id); - - if (vehicleIds.length === 0) { - return res.status(200).json({ distance: [], trips: [] }); - } - - let distanceData, tripData; - const today = new Date(); - const whereClause = { vehicleId: { [Op.in]: vehicleIds } }; - - switch (period) { - case "weekly": - whereClause.date = { - [Op.gte]: new Date(new Date().setDate(today.getDate() - 28)), - }; - distanceData = await DailyDistance.findAll({ - where: whereClause, - attributes: [ - [fn("YEARWEEK", col("date")), "period"], - [fn("SUM", col("distanceCovered")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - tripData = await DailyTrip.findAll({ - where: whereClause, - attributes: [ - [fn("YEARWEEK", col("date")), "period"], - [fn("SUM", col("tripCount")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - break; - - case "monthly": - const twelveMonthsAgo = new Date( - new Date().setMonth(today.getMonth() - 11), - ); - whereClause.date = { [Op.gte]: toYYYYMMDD(twelveMonthsAgo) }; - distanceData = await DailyDistance.findAll({ - where: whereClause, - attributes: [ - [fn("DATE_FORMAT", col("date"), "%Y-%m"), "period"], - [fn("SUM", col("distanceCovered")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - tripData = await DailyTrip.findAll({ - where: whereClause, - attributes: [ - [fn("DATE_FORMAT", col("date"), "%Y-%m"), "period"], - [fn("SUM", col("tripCount")), "total"], - ], - group: ["period"], - order: [[col("period"), "ASC"]], - }); - break; - - case "daily": - default: // Use toYYYYMMDD to ensure correct date comparison - const sevenDaysAgo = new Date(new Date().setDate(today.getDate() - 6)); - whereClause.date = { [Op.gte]: toYYYYMMDD(sevenDaysAgo) }; - distanceData = await DailyDistance.findAll({ - where: whereClause, - attributes: [ - ["date", "period"], - [fn("SUM", col("distanceCovered")), "total"], - ], - group: ["date"], - order: [["date", "ASC"]], - }); - tripData = await DailyTrip.findAll({ - where: whereClause, - attributes: [ - ["date", "period"], - [fn("SUM", col("tripCount")), "total"], - ], - group: ["date"], - order: [["date", "ASC"]], - }); - break; - } - - const formatData = (data, isDistance = false) => { - return data.map((d) => ({ - period: d.get("period"), - total: isDistance - ? parseFloat((d.get("total") / 1000).toFixed(2)) - : parseInt(d.get("total") || 0, 10), - })); - }; - - const formattedDistance = formatData(distanceData, true); - const formattedTrips = formatData(tripData); - - // Combine the data into a single structure, ensuring all periods are covered - const combinedData = {}; - const allPeriods = new Set([ - ...formattedDistance.map((d) => d.period), - ...formattedTrips.map((t) => t.period), - ]); - - allPeriods.forEach((period) => { - const dist = formattedDistance.find((d) => d.period === period); - const trip = formattedTrips.find((t) => t.period === period); - combinedData[period] = { - distance: dist ? dist.total : 0, - trips: trip ? trip.total : 0, - }; - }); - - res.status(200).json(combinedData); - } catch (error) { - next(error); - } -}; - -const getMyVehicles = async (req, res, next) => { - try { - const vehicles = await Vehicle.findAll({ - where: { ownerId: req.user.id }, - order: [["plateNumber", "ASC"]], - }); - res.status(200).json(vehicles); - } catch (error) { - next(error); - } -}; - -const getVehicleStats = async (req, res, next) => { - try { - const ownerId = req.user.id; - const today = new Date().toISOString().slice(0, 10); - - // Fetch vehicles owned by the MatAdmin - const vehicles = await Vehicle.findAll({ where: { ownerId } }); - - // For each vehicle, fetch its stats and latest feedback separately - const vehicleStats = await Promise.all( - vehicles.map(async (vehicle) => { - const dailyDistance = await DailyDistance.findOne({ - where: { vehicleId: vehicle.id, date: today }, - }); - const dailyTrip = await DailyTrip.findOne({ - where: { vehicleId: vehicle.id, date: today }, - }); - const latestFeedback = await Feedback.findAll({ - where: { vehicleId: vehicle.id }, - include: [{ model: User, attributes: ["name"] }], - limit: 5, - order: [["createdAt", "DESC"]], - }); - - return { - ...vehicle.toJSON(), // Convert Sequelize instance to plain object - DailyDistances: dailyDistance ? [dailyDistance.toJSON()] : [], - DailyTrips: dailyTrip ? [dailyTrip.toJSON()] : [], - Feedbacks: latestFeedback.map((f) => f.toJSON()), - }; - }), - ); - - res.status(200).json(vehicleStats); - } catch (error) { - next(error); - } -}; - -module.exports = { - createVehicle, - getDashboardData, - getMyVehicles, - getDashboardSummary, - getVehicleStats, - getVehicleChartData, - getFleetAggregateData, -}; diff --git a/server/controllers/stageController.js b/server/controllers/stageController.js deleted file mode 100644 index 4408602..0000000 --- a/server/controllers/stageController.js +++ /dev/null @@ -1,92 +0,0 @@ -const { Stage, User } = require("../models"); - -/** - * Fetches all stages from the database. - */ -const getAllStages = async (req, res, next) => { - try { - const stages = await Stage.findAll({ - order: [["name", "ASC"]], - }); - res.status(200).json(stages); - } catch (error) { - next(error); - } -}; - -const getSubscriptionStatus = async (req, res, next) => { - try { - const stageId = req.params.stageId; - const userId = req.user.id; - - const user = await User.findByPk(userId); - if (!user) return res.status(404).json({ message: "User not found" }); - - const isSubscribed = await user.hasAlertSubscription(stageId); - res.status(200).json({ isSubscribed }); - } catch (error) { - next(error); - } -}; - -const subscribeToStage = async (req, res, next) => { - try { - const stageId = req.params.stageId; - const userId = req.user.id; - - const user = await User.findByPk(userId); - if (!user) return res.status(404).json({ message: "User not found" }); - - if (!user.phoneNumber) { - return res - .status(400) - .json({ - message: - "Please add a phone number to your profile to subscribe for alerts.", - }); - } - - const stage = await Stage.findByPk(stageId); - if (!stage) return res.status(404).json({ message: "Stage not found" }); - - await user.addAlertSubscription(stage); - - res - .status(200) - .json({ - message: `Successfully subscribed to alerts for ${stage.name}.`, - }); - } catch (error) { - next(error); - } -}; - -const unsubscribeFromStage = async (req, res, next) => { - try { - const stageId = req.params.stageId; - const userId = req.user.id; - - const user = await User.findByPk(userId); - if (!user) return res.status(404).json({ message: "User not found" }); - - const stage = await Stage.findByPk(stageId); - if (!stage) return res.status(404).json({ message: "Stage not found" }); - - await user.removeAlertSubscription(stage); - - res - .status(200) - .json({ - message: `Successfully unsubscribed from alerts for ${stage.name}.`, - }); - } catch (error) { - next(error); - } -}; - -module.exports = { - getAllStages, - getSubscriptionStatus, - subscribeToStage, - unsubscribeFromStage, -}; diff --git a/server/middleware/authMiddleware.js b/server/middleware/authMiddleware.js deleted file mode 100644 index 0bf9321..0000000 --- a/server/middleware/authMiddleware.js +++ /dev/null @@ -1,50 +0,0 @@ -const jwt = require("jsonwebtoken"); -const { User } = require("../models"); - -const authMiddleware = (role) => { - return async (req, res, next) => { - const authHeader = req.header("Authorization"); - - if (!authHeader) { - return res - .status(401) - .json({ message: "Access denied. No token provided." }); - } - - const tokenParts = authHeader.split(" "); - if (tokenParts.length !== 2 || tokenParts[0] !== "Bearer") { - return res.status(400).json({ message: "Invalid token format" }); - } - - const token = tokenParts[1]; - - try { - const verified = jwt.verify(token, process.env.JWT_SECRET); - req.user = verified; - - // Check claims directly from the token to avoid DB lookups on every request. - if (verified.disabled) { - return res.status(403).json({ message: "Account disabled" }); - } - - // Check if the token was issued before the user's tokens were last invalidated. - if (verified.iat * 1000 < verified.tokensValidFrom) { - // tokensValidFrom is a timestamp in milliseconds - return res.status(401).json({ message: "Token has been invalidated" }); - } - - if (role && req.user.role !== role) { - return res - .status(403) - .json({ message: "Forbidden: Insufficient permissions" }); - } - - next(); - } catch (error) { - console.log("Error in token verification:", error); - res.status(401).json({ message: "Invalid token" }); - } - }; -}; - -module.exports = authMiddleware; diff --git a/server/middleware/errorHandler.js b/server/middleware/errorHandler.js deleted file mode 100644 index 19eba48..0000000 --- a/server/middleware/errorHandler.js +++ /dev/null @@ -1,31 +0,0 @@ -const { ValidationError, UniqueConstraintError } = require("sequelize"); - -const errorHandler = (err, req, res, next) => { - // Log the full error for debugging purposes on the server - console.error(err); - - // Handle malformed JSON request bodies - if (err instanceof SyntaxError && err.status === 400 && "body" in err) { - return res.status(400).json({ message: "Malformed JSON in request body" }); - } - - // Handle Sequelize validation errors specifically - if (err instanceof ValidationError || err instanceof UniqueConstraintError) { - const errors = err.errors.map((e) => ({ - message: e.message, - field: e.path, - })); - return res.status(400).json({ - message: "Validation failed", - errors, - }); - } - - // Send a generic, user-friendly message to the client - // to avoid leaking implementation details. - res - .status(500) - .json({ message: "An unexpected internal server error occurred." }); -}; - -module.exports = errorHandler; diff --git a/server/middleware/rateLimiters.js b/server/middleware/rateLimiters.js deleted file mode 100644 index bb5a4b6..0000000 --- a/server/middleware/rateLimiters.js +++ /dev/null @@ -1,82 +0,0 @@ -const rateLimit = require("express-rate-limit"); -const { redis } = require("../services/locationCache"); - -let store; - -// Only try to use RedisStore if Redis is configured and the package is installed. -if (redis) { - try { - const { RedisStore } = require("rate-limit-redis"); - store = new RedisStore({ - // @ts-ignore - sendCommand: (...args) => redis.call(...args), - }); - console.log("✅ Rate limiter connected to Redis."); - } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { - console.warn( - "⚠️ `rate-limit-redis` package not found. Rate limiting will use in-memory store.", - ); - } else { - throw e; - } - } -} - -// General limiter for authentication actions -const authLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100, // Limit each IP to 100 requests per windowMs - store, - standardHeaders: true, - legacyHeaders: false, - message: "Too many requests from this IP, please try again after 15 minutes", -}); - -// A stricter limiter for potentially costly operations like SMS -const subscriptionLimiter = rateLimit({ - windowMs: 10 * 60 * 1000, // 10 minutes - max: 30, // Limit each IP to 30 subscription changes per 10 minutes - store, - standardHeaders: true, - legacyHeaders: false, - message: "Too many subscription requests. Please try again later.", -}); - -// General limiter for API endpoints with authorization -const apiLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100, // Limit each IP to 100 requests per windowMs - store, - standardHeaders: true, - legacyHeaders: false, - message: "Too many requests from this IP, please try again after 15 minutes", -}); - -// Stricter limiter for admin operations -const adminLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 50, // Limit admin operations to 50 requests per 15 minutes - store, - standardHeaders: true, - legacyHeaders: false, - message: "Too many admin requests. Please try again later.", -}); - -// Stricter limiter for dashboard/data retrieval -const dashboardLimiter = rateLimit({ - windowMs: 1 * 60 * 1000, // 1 minute - max: 30, // Limit to 30 requests per minute - store, - standardHeaders: true, - legacyHeaders: false, - message: "Too many dashboard requests. Please try again later.", -}); - -module.exports = { - authLimiter, - subscriptionLimiter, - apiLimiter, - adminLimiter, - dashboardLimiter, -}; diff --git a/server/models/DailyDistance.js b/server/models/DailyDistance.js deleted file mode 100644 index 66005ce..0000000 --- a/server/models/DailyDistance.js +++ /dev/null @@ -1,41 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const DailyDistance = sequelize.define( - "DailyDistance", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - vehicleId: { - type: DataTypes.INTEGER, - allowNull: false, - references: { - model: "Vehicles", - key: "id", - }, - }, - date: { - type: DataTypes.DATEONLY, // Stores date as 'YYYY-MM-DD' - allowNull: false, - }, - distanceCovered: { - type: DataTypes.FLOAT, // Distance in meters - allowNull: false, - defaultValue: 0, - }, - }, - { - indexes: [ - { - unique: true, - fields: ["vehicleId", "date"], - }, - { fields: ["vehicleId"] }, - ], - }, -); - -module.exports = DailyDistance; diff --git a/server/models/DailyTrip.js b/server/models/DailyTrip.js deleted file mode 100644 index 95a0240..0000000 --- a/server/models/DailyTrip.js +++ /dev/null @@ -1,41 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const DailyTrip = sequelize.define( - "DailyTrip", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - vehicleId: { - type: DataTypes.INTEGER, - allowNull: false, - references: { - model: "Vehicles", - key: "id", - }, - }, - date: { - type: DataTypes.DATEONLY, // Stores date as 'YYYY-MM-DD' - allowNull: false, - }, - tripCount: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - }, - }, - { - indexes: [ - { - unique: true, - fields: ["vehicleId", "date"], - }, - { fields: ["vehicleId"] }, - ], - }, -); - -module.exports = DailyTrip; diff --git a/server/models/Feedback.js b/server/models/Feedback.js deleted file mode 100644 index fb4502d..0000000 --- a/server/models/Feedback.js +++ /dev/null @@ -1,38 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const Feedback = sequelize.define( - "Feedback", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - vehicleId: { - type: DataTypes.INTEGER, - allowNull: false, - references: { model: "Vehicles", key: "id" }, - }, - userId: { - type: DataTypes.INTEGER, - allowNull: false, - references: { model: "Users", key: "id" }, - }, - rating: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { min: 1, max: 5 }, - }, - comment: { - type: DataTypes.TEXT, - allowNull: true, - }, - }, - { - timestamps: true, - indexes: [{ fields: ["vehicleId"] }, { fields: ["userId"] }], - }, -); - -module.exports = Feedback; diff --git a/server/models/Stage.js b/server/models/Stage.js deleted file mode 100644 index a053352..0000000 --- a/server/models/Stage.js +++ /dev/null @@ -1,22 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const Stage = sequelize.define( - "Stage", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - }, - latitude: { type: DataTypes.DOUBLE, allowNull: false }, - longitude: { type: DataTypes.DOUBLE, allowNull: false }, - } -); - -module.exports = Stage; \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js deleted file mode 100644 index 5b139dd..0000000 --- a/server/models/User.js +++ /dev/null @@ -1,67 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const User = sequelize.define( - "User", - { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false }, - email: { type: DataTypes.STRING, allowNull: false }, - password: { - type: DataTypes.STRING, - allowNull: true, // Allow null for Google-based signups - }, - role: { - type: DataTypes.ENUM("Passenger", "Admin", "MatAdmin"), - allowNull: false, - }, - googleId: { - type: DataTypes.STRING, - allowNull: true, - }, - phoneNumber: { - type: DataTypes.STRING, - allowNull: true, - }, - profilePictureUrl: { - type: DataTypes.STRING, - allowNull: true, - }, - twoFactorSecret: { - type: DataTypes.STRING, - allowNull: true, - }, - twoFactorEnabled: { - type: DataTypes.BOOLEAN, - defaultValue: false, - allowNull: false, - }, - disabled: { - type: DataTypes.BOOLEAN, - defaultValue: false, - allowNull: false, - }, - tokensValidFrom: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: DataTypes.NOW, // Default to current timestamp - }, - }, - { - timestamps: true, - indexes: [ - { - unique: true, - fields: ["email"], - name: "idx_users_email", - }, - { - unique: true, - fields: ["googleId"], - name: "idx_users_googleid", - }, - ], - }, -); - -module.exports = User; diff --git a/server/models/Vehicle.js b/server/models/Vehicle.js deleted file mode 100644 index bfef40d..0000000 --- a/server/models/Vehicle.js +++ /dev/null @@ -1,59 +0,0 @@ -const { DataTypes } = require("sequelize"); -const sequelize = require("../config/database"); - -const Vehicle = sequelize.define( - "Vehicle", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - carId: { - type: DataTypes.STRING, - allowNull: false, - }, - plateNumber: { - type: DataTypes.STRING, - allowNull: false, - }, - ownerId: { - type: DataTypes.INTEGER, - allowNull: true, // Can be null if created by Admin and not yet assigned - references: { - model: "Users", // 'Users' is the table name - key: "id", - }, - }, - lastLatitude: { - type: DataTypes.DOUBLE, - allowNull: true, - }, - lastLongitude: { - type: DataTypes.DOUBLE, - allowNull: true, - }, - isAtStage: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - }, - { - paranoid: true, // Enable soft deletes - indexes: [ - // Create a unique index on carId - { unique: true, fields: ["carId"] }, - // Create a unique index on plateNumber - { unique: true, fields: ["plateNumber"] }, - // Add an index on the foreign key for performance - { fields: ["ownerId"] }, - ], - }, -); - -// Note: The above is the standard way to define indexes. If you still face issues, -// you could try a composite unique index on both fields if that fits your logic: -// { unique: true, fields: ['carId', 'plateNumber'] } - -module.exports = Vehicle; diff --git a/server/models/index.js b/server/models/index.js deleted file mode 100644 index f75971b..0000000 --- a/server/models/index.js +++ /dev/null @@ -1,78 +0,0 @@ -const sequelize = require("../config/database"); -const User = require("./User"); -const Stage = require("./Stage"); -const Vehicle = require("./Vehicle"); -const DailyDistance = require("./DailyDistance"); -const DailyTrip = require("./DailyTrip"); -const Feedback = require("./Feedback"); - -// A Stage can have many vehicles, and a Vehicle can belong to many stages (in different routes) -// This creates a join table `StageVehicles` -Stage.belongsToMany(Vehicle, { through: "StageVehicles" }); -Vehicle.belongsToMany(Stage, { through: "StageVehicles" }); - -// A User can subscribe to many stages for alerts, and a Stage can have many subscribers. -// This creates a join table `UserStageSubscriptions` -User.belongsToMany(Stage, { - through: "UserStageSubscriptions", - as: "AlertSubscriptions", -}); -Stage.belongsToMany(User, { - through: "UserStageSubscriptions", - as: "Subscribers", -}); - -// A User (MatAdmin) can own many Vehicles. -User.hasMany(Vehicle, { - foreignKey: "ownerId", - as: "OwnedVehicles", - onDelete: "SET NULL", -}); -Vehicle.belongsTo(User, { foreignKey: "ownerId", as: "Owner" }); - -// A Vehicle can have many DailyDistance records. -Vehicle.hasMany(DailyDistance, { - foreignKey: "vehicleId", - onDelete: "CASCADE", -}); -DailyDistance.belongsTo(Vehicle, { foreignKey: "vehicleId" }); - -// A Vehicle can have many DailyTrip records. -Vehicle.hasMany(DailyTrip, { foreignKey: "vehicleId", onDelete: "CASCADE" }); -DailyTrip.belongsTo(Vehicle, { foreignKey: "vehicleId" }); - -// A Vehicle can have many Feedback entries. A User can give many Feedbacks. -Vehicle.hasMany(Feedback, { foreignKey: "vehicleId", onDelete: "CASCADE" }); -Feedback.belongsTo(Vehicle, { foreignKey: "vehicleId" }); -User.hasMany(Feedback, { foreignKey: "userId", onDelete: "CASCADE" }); -Feedback.belongsTo(User, { foreignKey: "userId" }); - -const syncDB = async () => { - try { - // In a production environment, you should use migrations instead of `sync`. - // `sync({ alter: true })` is dangerous and can lead to data loss. - // For development, `sync()` is acceptable. For production, this should be disabled - // and a migration tool like Sequelize-CLI or Umzug should be used instead. - if (process.env.NODE_ENV !== "production") { - await sequelize.sync(); - console.log( - "✅ Database synchronized (Note: Use migrations for production).", - ); - } - } catch (error) { - console.error("❌ Error synchronizing the database:", error); - // Re-throw the error to be caught by the server startup logic. - throw error; - } -}; - -module.exports = { - sequelize, - syncDB, - User, - Stage, - Vehicle, - DailyDistance, - DailyTrip, - Feedback, -}; diff --git a/server/routes/adminRoutes.js b/server/routes/adminRoutes.js deleted file mode 100644 index 3ca3833..0000000 --- a/server/routes/adminRoutes.js +++ /dev/null @@ -1,91 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { body, param } = require("express-validator"); -const authMiddleware = require("../middleware/authMiddleware"); -const { adminLimiter, dashboardLimiter } = require("../middleware/rateLimiters"); -const { - getAllStages, - createStage, - getAllVehicles, - createVehicle, - deleteVehicle, - assignVehicleToStage, - removeVehicleFromStage, - getDashboardData, - getAllFeedback, - getAllUsers, - updateUserByAdmin, - deleteUserByAdmin, - createUserByAdmin, - getAllMatAdmins, -} = require("../controllers/adminController"); - -// All routes in this file are for Admins only -router.use(adminLimiter); -router.use(authMiddleware("Admin")); - -// Dashboard data -router.get("/dashboard-data", dashboardLimiter, getDashboardData); - -// User Management -router.get("/users", getAllUsers); -router.post( - "/users", - body("name", "Name is required").notEmpty().trim().escape(), - body("email", "Please provide a valid email").isEmail().normalizeEmail(), - body("password", "Password must be at least 6 characters long").isLength({ - min: 6, - }), - body("role") - .isIn(["Admin", "MatAdmin"]) - .withMessage("Invalid role specified"), - createUserByAdmin, -); -router.put( - "/users/:id", - body("disabled", "Disabled status must be a boolean").isBoolean(), - updateUserByAdmin, -); -router.delete("/users/:id", deleteUserByAdmin); - -router.get("/feedback", getAllFeedback); -router.get("/mat-admins", getAllMatAdmins); -router.get("/stages", getAllStages); -router.post( - "/stages", - body("name", "Stage name is required").notEmpty().trim(), - body("latitude", "A valid latitude is required").isFloat({ - min: -90, - max: 90, - }), - body("longitude", "A valid longitude is required").isFloat({ - min: -180, - max: 180, - }), - createStage, -); -router.get("/vehicles", getAllVehicles); -router.post( - "/vehicles", - body("carId", "Car ID is required").notEmpty().trim(), - body("plateNumber", "Plate number is required").notEmpty().trim(), - body("ownerId", "Owner ID must be an integer") - .optional({ checkFalsy: true }) - .isInt(), - createVehicle, -); -router.delete("/vehicles/:id", deleteVehicle); -router.post( - "/stages/assign-vehicle", - body("stageId", "Stage ID must be a positive integer").isInt({ min: 1 }), - body("vehicleId", "Vehicle ID must be a positive integer").isInt({ min: 1 }), - assignVehicleToStage, -); -router.delete( - "/stages/:stageId/vehicles/:vehicleId", - param("stageId", "Stage ID must be a positive integer").isInt({ min: 1 }), - param("vehicleId", "Vehicle ID must be a positive integer").isInt({ min: 1 }), - removeVehicleFromStage, -); - -module.exports = router; diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js deleted file mode 100644 index 72804e4..0000000 --- a/server/routes/authRoutes.js +++ /dev/null @@ -1,121 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { body, param } = require("express-validator"); -const { - loginUser, - createUser, - updateProfile, - me, - refreshAccessToken, - logoutUser, - requestPasswordReset, - resetPassword, - generateTwoFactorSecret, - verifyTwoFactorSecret, - verifyLoginTwoFactor, - disableTwoFactor, -} = require("../controllers/authController"); -const authMiddleware = require("../middleware/authMiddleware"); -const passport = require("passport"); - -// Login Route -router.post( - "/login", - body("email", "Please provide a valid email").isEmail().normalizeEmail(), - body("password", "Password cannot be empty").notEmpty(), - loginUser, -); - -// Create User Route (For Initial Setup) -router.post( - "/register", - body("name", "Name is required").notEmpty().trim().escape(), - body("email", "Please provide a valid email").isEmail().normalizeEmail(), - body("password", "Password must be at least 6 characters long").isLength({ - min: 6, - }), - createUser, -); - -// Check login status -router.get("/me/admin", authMiddleware("Admin"), me); -router.get("/me/passenger", authMiddleware("Passenger"), me); -router.get("/me/mat-admin", authMiddleware("MatAdmin"), me); - -// Update user profile -router.put( - "/me", - authMiddleware(), - body("name").optional().trim().escape(), - body("phoneNumber") - .optional({ checkFalsy: true }) - .isString() - .withMessage("Invalid phone number"), - updateProfile, -); - -// Refresh Token -router.post("/refresh-token", refreshAccessToken); - -// Log Out -router.post("/logout", authMiddleware(), logoutUser); - -//Route to request password reset\ -router.post("/requestPasswordReset", requestPasswordReset); - -// Route to reset password -router.post( - "/resetpassword/:id/:token", - param("id", "Invalid user ID").isInt({ min: 1 }), - param("token", "Invalid token format").isJWT(), - body("password", "Password must be at least 6 characters long").isLength({ - min: 6, - }), - resetPassword, -); - -// --- Two-Factor Authentication Routes --- -router.post("/2fa/setup", authMiddleware(), generateTwoFactorSecret); -router.post("/2fa/verify", authMiddleware(), verifyTwoFactorSecret); -router.post("/2fa/disable", authMiddleware(), disableTwoFactor); -router.post("/2fa/login", verifyLoginTwoFactor); - -// --- Google OAuth Routes --- - -// 1. Route to start the Google sign-in flow -router.get("/google", (req, res, next) => { - passport.authenticate("google", { scope: ["profile", "email"] })( - req, - res, - next, - ); -}); - -// 2. Callback route that Google redirects to after authentication -router.get( - "/google/callback", - (req, res, next) => { - passport.authenticate( - "google", - { failureRedirect: "/login.html?error=auth_failed", session: false }, - (err, user, info) => { - if (err) { - console.error(" Passport authenticate error:", err); - return next(err); - } - if (!user) { - console.log( - " User not found or authentication failed. Info:", - info, - ); - return res.redirect("/login.html?error=auth_failed"); - } - req.user = user; // Manually attach user to request - next(); - }, - )(req, res, next); - }, - require("../controllers/authController").googleCallback, -); - -module.exports = router; diff --git a/server/routes/etaRoutes.js b/server/routes/etaRoutes.js deleted file mode 100644 index 69f6193..0000000 --- a/server/routes/etaRoutes.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { getStageEta } = require("../controllers/etaController"); -const authMiddleware = require("../middleware/authMiddleware"); -const { apiLimiter } = require("../middleware/rateLimiters"); - -router.get('/:stageId', apiLimiter, authMiddleware(), getStageEta); - -module.exports = router; diff --git a/server/routes/feedbackRoutes.js b/server/routes/feedbackRoutes.js deleted file mode 100644 index 663cd95..0000000 --- a/server/routes/feedbackRoutes.js +++ /dev/null @@ -1,24 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { body } = require("express-validator"); -const authMiddleware = require("../middleware/authMiddleware"); -const { apiLimiter } = require("../middleware/rateLimiters"); -const { - submitFeedback, - getAllVehiclesForFeedback, -} = require("../controllers/feedbackController"); - -// All routes in this file are for authenticated passengers -router.use(apiLimiter); -router.use(authMiddleware("Passenger")); - -router.get("/vehicles", getAllVehiclesForFeedback); -router.post( - "/", - body("vehicleId", "Vehicle ID is required").isInt(), - body("rating", "Rating must be between 1 and 5").isInt({ min: 1, max: 5 }), - body("comment").optional().trim().escape(), - submitFeedback, -); - -module.exports = router; diff --git a/server/routes/matAdminRoutes.js b/server/routes/matAdminRoutes.js deleted file mode 100644 index d244a67..0000000 --- a/server/routes/matAdminRoutes.js +++ /dev/null @@ -1,33 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { body } = require("express-validator"); -const authMiddleware = require("../middleware/authMiddleware"); -const { adminLimiter, dashboardLimiter } = require("../middleware/rateLimiters"); -const { - getDashboardData, - createVehicle, - getMyVehicles, - getDashboardSummary, - getVehicleStats, - getVehicleChartData, - getFleetAggregateData, -} = require("../controllers/matAdminController"); - -// All routes in this file are for MatAdmins only -router.use(adminLimiter); -router.use(authMiddleware("MatAdmin")); - -router.get("/dashboard-summary", dashboardLimiter, getDashboardSummary); -router.get("/dashboard-data", dashboardLimiter, getDashboardData); // For live vehicle status -router.get("/vehicle-stats", getVehicleStats); -router.get("/fleet-aggregate-data", getFleetAggregateData); -router.get("/vehicles/:vehicleId/chart-data", getVehicleChartData); -router.post( - "/vehicles", - body("carId", "Car ID is required").notEmpty().trim(), - body("plateNumber", "Plate number is required").notEmpty().trim(), - createVehicle, -); -router.get("/vehicles", getMyVehicles); - -module.exports = router; diff --git a/server/routes/stageRoutes.js b/server/routes/stageRoutes.js deleted file mode 100644 index 0c90942..0000000 --- a/server/routes/stageRoutes.js +++ /dev/null @@ -1,30 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { - getAllStages, - getSubscriptionStatus, - subscribeToStage, - unsubscribeFromStage, -} = require("../controllers/stageController"); -const authMiddleware = require("../middleware/authMiddleware"); -const { subscriptionLimiter, apiLimiter } = require("../middleware/rateLimiters"); - -// This route is for any authenticated user (Passenger or Admin) -router.get("/", authMiddleware(), apiLimiter, getAllStages); - -// Routes for managing SMS alert subscriptions -router.get("/:stageId/subscription", authMiddleware(), apiLimiter, getSubscriptionStatus); -router.post( - "/:stageId/subscribe", - authMiddleware(), - subscriptionLimiter, - subscribeToStage, -); -router.post( - "/:stageId/unsubscribe", - authMiddleware(), - subscriptionLimiter, - unsubscribeFromStage, -); - -module.exports = router; diff --git a/server/server.js b/server/server.js deleted file mode 100644 index 52a600b..0000000 --- a/server/server.js +++ /dev/null @@ -1,82 +0,0 @@ -require("dotenv").config(); -const path = require("path"); -const express = require("express"); -const cookieParser = require("cookie-parser"); -const { syncDB } = require("./models"); -const app = express(); -const cors = require("cors"); -const helmet = require("helmet"); -const passport = require("passport"); -const csrf = require("csurf"); - -const isPkg = typeof process.pkg !== "undefined"; -const basePath = isPkg ? path.dirname(process.execPath) : __dirname; -app.use(express.static(path.join(basePath, "../public"))); - -// Middleware -// Trust the first proxy in front of the app (e.g., Nginx, Heroku, etc.) -// This is crucial for rate limiting to work correctly behind a proxy. -app.set("trust proxy", 1); -app.use(helmet()); // Adds various security headers -app.use( - cors({ - origin: process.env.CORS_ORIGIN || "http://localhost:5000", // Restrict to frontend origin - credentials: true, // Allow cookies to be sent - }), -); -app.use(cookieParser()); -app.use(express.json()); - -// CSRF protection middleware -const csrfProtection = csrf({ cookie: true }); -app.use(csrfProtection); - -app.use(passport.initialize()); - -// Import rate limiters -const { authLimiter } = require("./middleware/rateLimiters"); - -// This should be after dotenv config and passport.initialize -require("./config/passport"); // Import the passport configuration - -// Routes -app.use("/api/auth", authLimiter, require("./routes/authRoutes")); -app.use("/api/eta", require("./routes/etaRoutes")); // ETA routes are already authenticated -app.use("/api/stages", require("./routes/stageRoutes")); // Rate limiting is applied within the route file -app.use("/api/admin", require("./routes/adminRoutes")); -app.use("/api/feedback", require("./routes/feedbackRoutes")); -app.use("/api/mat-admin", require("./routes/matAdminRoutes")); - -// --- Background ETA Update Service --- -const { startEtaUpdateCycle } = require("./services/cronService"); -const UPDATE_INTERVAL_SECONDS = 30; - -// --- Centralized Error Handler --- -const errorHandler = require("./middleware/errorHandler"); -app.use(errorHandler); - -// --- Start Server Function --- -const startServer = async () => { - try { - // 1. Connect to and sync the database. - await syncDB(); - - // 2. If DB sync is successful, start the server. - const PORT = process.env.PORT || 5000; - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log("Open front end at http://localhost:5000/"); - - // 3. Start the background ETA update cycle using node-cron. - startEtaUpdateCycle(UPDATE_INTERVAL_SECONDS); - }); - } catch (error) { - console.error( - "❌ Failed to start server due to database connection issues. Please check your .env file and ensure your database is running.", - ); - process.exit(1); // Exit with a failure code. - } -}; - -// --- Run the application --- -startServer(); diff --git a/server/services/cronService.js b/server/services/cronService.js deleted file mode 100644 index 313a8e6..0000000 --- a/server/services/cronService.js +++ /dev/null @@ -1,208 +0,0 @@ -const cron = require("node-cron"); -const { sequelize, Stage, Vehicle, User } = require("../models"); -const { calculateEtasForStage } = require("./etaCalculationService"); -const { sendSms } = require("./smsService"); -const { - updateStageEtas, - getAllVehicleLocations, -} = require("./firebaseService"); -const { setLocations } = require("./locationCache"); -const { updateVehicleDistance } = require("./distanceTrackingService"); - -let isCycleRunning = false; - -const runEtaUpdateCycle = async () => { - if (isCycleRunning) { - console.log( - "ETA update cycle is already running. Skipping this execution.", - ); - return; - } - isCycleRunning = true; - console.log("Running ETA update cycle..."); - try { - // Outer try-catch for fetching stages - const stages = await Stage.findAll({ - include: [ - // Eager load all necessary data at the start of the cycle - { model: Vehicle, through: { attributes: [] }, required: false }, - { model: User, as: "Subscribers", attributes: ["id", "phoneNumber"] }, - ], - }); - - // --- 1. Read Phase: Get all vehicle data at once --- - const allVehicles = await Vehicle.findAll(); - const allFirebaseLocations = await getAllVehicleLocations(); // Single call to Firebase - const vehicleLocations = new Map(); - - if (allFirebaseLocations) { - for (const vehicle of allVehicles) { - const location = allFirebaseLocations[vehicle.carId]; - if (location) { - vehicleLocations.set(vehicle.id, location); - } - } - } - - // Update the shared cache with the newly fetched locations - setLocations(vehicleLocations); - - // This map will track the final calculated status for each vehicle across all stages. - const vehicleFinalStatuses = new Map(); - - // --- 2. Calculation Phase: Use the fetched data --- - for (const stage of stages) { - try { - // Inner try-catch for processing each stage - const vehicleEtas = await calculateEtasForStage( - stage, - vehicleLocations, - ); - - // Transform the array into an object with carId as the key for Firebase - const etasForFirebase = vehicleEtas.reduce((acc, vehicle) => { - acc[vehicle.carId] = { - plateNumber: vehicle.plateNumber, - eta: vehicle.eta, - status: vehicle.status, - }; - return acc; - }, {}); - - await updateStageEtas(stage.name, etasForFirebase); - - // Store the status of each vehicle for this stage. - for (const vehicle of vehicleEtas) { - // If the vehicle is 'Arrived' at this stage, its final status for the cycle is 'Arrived'. - // Otherwise, only set the status if it hasn't already been marked as 'Arrived' by another stage. - if ( - vehicle.status === "Arrived" || - !vehicleFinalStatuses.has(vehicle.id) - ) { - vehicleFinalStatuses.set(vehicle.id, vehicle.status); - } - } - - // --- SMS Alert Logic --- - for (const vehicle of vehicleEtas) { - const vehicleInstance = allVehicles.find((v) => v.id === vehicle.id); - if (!vehicleInstance) continue; - - if (vehicle.status === "Arrived") { - // --- New Trip Counting Logic --- - // Use a transaction with row-level locking to prevent race conditions - await sequelize.transaction(async (t) => { - const freshVehicle = await Vehicle.findByPk(vehicle.id, { - transaction: t, // Find within the transaction - }); - - if (freshVehicle && !freshVehicle.isAtStage) { - const today = new Date().toISOString().slice(0, 10); - // Use a raw query with ON DUPLICATE KEY UPDATE for an atomic increment. - // This is more performant and less prone to deadlocks than findOrCreate/increment. - await sequelize.query( - `INSERT INTO DailyTrips (vehicleId, date, tripCount, createdAt, updatedAt) - VALUES (:vehicleId, :date, 1, NOW(), NOW()) - ON DUPLICATE KEY UPDATE tripCount = tripCount + 1, updatedAt = NOW()`, - { - replacements: { vehicleId: vehicle.id, date: today }, - type: sequelize.QueryTypes.INSERT, - transaction: t, - }, - ); - - await freshVehicle.update( - { isAtStage: true }, - { transaction: t }, - ); - console.log(`Trip counted for ${vehicle.plateNumber}.`); - } - }); - - // We iterate over a copy of the subscribers array because we are modifying the subscription - // in the loop, which could cause issues with the iterator. - const smsTasks = stage.Subscribers.map(async (subscriber) => { - try { - const isStillSubscribed = await subscriber.hasAlertSubscription( - stage.id, - ); - if (subscriber.phoneNumber && isStillSubscribed) { - const message = `Matatu ${vehicle.plateNumber} has arrived at ${stage.name}.`; - console.log( - `Sending SMS to ${subscriber.phoneNumber}: "${message}"`, - ); - const smsResponse = await sendSms( - subscriber.phoneNumber, - message, - ); - // Only unsubscribe if the SMS was successfully sent (status code 101). - if ( - smsResponse?.SMSMessageData?.Recipients?.[0]?.statusCode === - 101 - ) { - await subscriber.removeAlertSubscription(stage); - } else { - console.warn( - `SMS to ${subscriber.phoneNumber} may have failed or was skipped. User will not be unsubscribed.`, - ); - } - } - } catch (smsError) { - // Log errors for individual SMS/DB failures without stopping the whole batch. - console.error( - `Failed to process SMS for user ${subscriber.id}:`, - smsError, - ); - } - }); - // Process all SMS notifications concurrently for this arrival event. - await Promise.allSettled(smsTasks); - } - } - } catch (stageError) { - console.error(`❌ Error processing stage ${stage.name}:`, stageError); - } - } - - // --- 2.5. State Reset Phase --- - // Now that all stages are processed, reset the isAtStage flag for any vehicle - // that is no longer 'Arrived' at ANY of its assigned stages. - for (const vehicle of allVehicles) { - if ( - vehicle.isAtStage && - vehicleFinalStatuses.get(vehicle.id) !== "Arrived" - ) { - await vehicle.update({ isAtStage: false }); - } - } - // --- 3. Write Phase: Update distances and last known locations --- - console.log("Updating vehicle distances and locations..."); - for (const vehicle of allVehicles) { - const currentLocation = vehicleLocations.get(vehicle.id); - if (currentLocation) { - // This service now handles both distance calculation and conditionally updating the last known location. - await updateVehicleDistance(vehicle, currentLocation); - } - } - - console.log("✅ ETA update cycle completed successfully."); - } catch (error) { - console.error("❌ Error during ETA update cycle:", error); - } finally { - isCycleRunning = false; - } -}; - -const startEtaUpdateCycle = (intervalSeconds) => { - // Schedule the task to run every `intervalSeconds`. - // The cron job ensures that a new job won't start until the previous one has finished. - cron.schedule(`*/${intervalSeconds} * * * * *`, runEtaUpdateCycle, { - scheduled: true, - timezone: "UTC", - }); - console.log( - `✅ ETA update cycle scheduled to run every ${intervalSeconds} seconds.`, - ); -}; - -module.exports = { startEtaUpdateCycle }; diff --git a/server/services/distanceTrackingService.js b/server/services/distanceTrackingService.js deleted file mode 100644 index 0507875..0000000 --- a/server/services/distanceTrackingService.js +++ /dev/null @@ -1,57 +0,0 @@ -const { Vehicle, DailyDistance } = require("../models"); -const { getVehicleLocation } = require("./firebaseService"); -const { Op } = require("sequelize"); -const { haversineDistance } = require("./geolocationService"); - -/** - * Updates the daily distance for a single vehicle. - * @param {object} vehicle - The Sequelize vehicle instance. - * @param {object} currentLocation - The vehicle's current location { latitude, longitude }. - */ -async function updateVehicleDistance(vehicle, currentLocation) { - const { latitude: lat2, longitude: lon2 } = currentLocation; - const { lastLatitude: lat1, lastLongitude: lon1 } = vehicle; - - // If we have previous coordinates from the last cycle, calculate the distance moved - if (lat1 && lon1) { - const distanceMoved = haversineDistance(lat1, lon1, lat2, lon2); - - // A threshold is crucial to prevent accumulation of false distance from GPS drift. - // Only count movement if it's greater than a reasonable threshold (e.g., 10 meters). - if (distanceMoved > 10) { - const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD - - // Find or create a daily distance record for this vehicle for today - const [dailyDistance] = await DailyDistance.findOrCreate({ - where: { - vehicleId: vehicle.id, - date: today, - }, - defaults: { - vehicleId: vehicle.id, - date: today, - distanceCovered: 0, - }, - }); - - // Add the new distance to the existing total for the day - await dailyDistance.increment("distanceCovered", { by: distanceMoved }); - - // Update the last known location for the *next* cycle because significant movement was detected. - await vehicle.update({ - lastLatitude: lat2, - lastLongitude: lon2, - }); - } - // If distanceMoved <= 10, we do NOT update the last known location to prevent losing small increments. - } else { - // If there's no previous location, this is the first time we're seeing the vehicle. - // Set the current one as the baseline for the next cycle. - await vehicle.update({ - lastLatitude: lat2, - lastLongitude: lon2, - }); - } -} - -module.exports = { updateVehicleDistance }; diff --git a/server/services/etaCalculationService.js b/server/services/etaCalculationService.js deleted file mode 100644 index 399f123..0000000 --- a/server/services/etaCalculationService.js +++ /dev/null @@ -1,145 +0,0 @@ -const { getVehicleLocation } = require("./firebaseService"); -const { getDirectionsInfo } = require("./googleMapsService"); -const { calculateBearing, haversineDistance } = require("./geolocationService"); - -/** - * Calculates ETAs for all vehicles assigned to a specific stage. - * @param {object} stage - A Sequelize Stage object. - * @param {Map} vehicleLocations - A map of vehicleId to its current location. - * @returns {Promise} A promise that resolves to an array of vehicle ETA objects. - */ -const calculateEtasForStage = async (stage, vehicleLocations) => { - if (!stage || !stage.Vehicles) { - return []; - } - - const vehicleEtas = await Promise.all( - stage.Vehicles.map(async (vehicle) => { - const carId = vehicle.carId; - const currentVehicleLocation = vehicleLocations.get(vehicle.id); - - if ( - !currentVehicleLocation || - !currentVehicleLocation.latitude || - !currentVehicleLocation.longitude - ) { - console.log(`Location data not found or incomplete for ${carId}`); - return { - id: vehicle.id, - carId, - plateNumber: vehicle.plateNumber, - eta: "N/A", - status: "Offline", - }; - } - - const origin = { - latitude: currentVehicleLocation.latitude, - longitude: currentVehicleLocation.longitude, - }; - const destination = { - latitude: stage.latitude, - longitude: stage.longitude, - }; - - // --- Geofencing Check --- - // First, calculate the direct distance. Only query Google Maps if the vehicle is within a reasonable radius. - // This prevents excessive API calls for vehicles that are very far away. - const GEOFENCE_RADIUS_METERS = 5000; // 5km - const distanceToStage = haversineDistance( - origin.latitude, - origin.longitude, - destination.latitude, - destination.longitude, - ); - - if (distanceToStage > GEOFENCE_RADIUS_METERS) { - // The vehicle is outside the relevant zone, no need to calculate a route. - return { - id: vehicle.id, - carId, - plateNumber: vehicle.plateNumber, - eta: "N/A", - status: "Too Far", - }; - } - - const directions = await getDirectionsInfo(origin, destination); - - if (!directions || !directions.distance || !directions.duration) { - console.log(`Could not calculate directions for ${carId}`); - return { - id: vehicle.id, - carId, - plateNumber: vehicle.plateNumber, - eta: "N/A", - status: "No Route", - }; - } - - const duration = directions.duration_in_traffic || directions.duration; - const distanceInMeters = directions.distance.value; - let status = "Approaching"; - - if (distanceInMeters < 50) { - status = "Arrived"; - } else { - // Direction check for vehicles that are not at the stage - const previousLocation = - vehicle.lastLatitude && vehicle.lastLongitude - ? { - latitude: vehicle.lastLatitude, - longitude: vehicle.lastLongitude, - } - : null; - - // Check if the vehicle has moved significantly to avoid noise from GPS drift - const hasMoved = - previousLocation && - haversineDistance( - previousLocation.latitude, - previousLocation.longitude, - origin.latitude, - origin.longitude, - ) > 10; // Threshold of 10 meters - - if (previousLocation && hasMoved) { - const vehicleBearing = calculateBearing(previousLocation, origin); - const bearingToStage = calculateBearing(origin, destination); - - // Calculate the difference in angle. The result is between 0 and 180. - let angleDiff = Math.abs(vehicleBearing - bearingToStage); - if (angleDiff > 180) { - angleDiff = 360 - angleDiff; - } - - // If the angle is greater than 90 degrees, the vehicle is moving away from the stage. - if (angleDiff > 90) { - status = "Departed"; - } - } - } - - return { - id: vehicle.id, - carId: carId, - plateNumber: vehicle.plateNumber, - // Only show ETA if the vehicle is actually approaching - eta: - status === "Approaching" - ? duration.text - : status === "Arrived" - ? "Arrived" - : "N/A", - status: status, - }; - }), - ); - - // Filter out vehicles that have departed so they don't show up on the passenger's screen for that stage - return vehicleEtas.filter( - (v) => v.status !== "Departed" && v.status !== "Too Far", - ); -}; - -module.exports = { calculateEtasForStage }; diff --git a/server/services/firebaseService.js b/server/services/firebaseService.js deleted file mode 100644 index 48d8b01..0000000 --- a/server/services/firebaseService.js +++ /dev/null @@ -1,88 +0,0 @@ -const admin = require("firebase-admin"); -const path = require("path"); -const fs = require("fs"); - -const serviceAccountPath = path.join( - __dirname, - "../config/firebase-service-account.json", -); -let firebaseInitialized = false; - -let serviceAccount; - -if (process.env.FIREBASE_CREDENTIALS_BASE64) { - try { - const decodedString = Buffer.from( - process.env.FIREBASE_CREDENTIALS_BASE64, - "base64", - ).toString("utf-8"); - serviceAccount = JSON.parse(decodedString); - } catch (e) { - console.error("❌ Failed to parse FIREBASE_CREDENTIALS_BASE64:", e); - } -} else if (fs.existsSync(serviceAccountPath)) { - serviceAccount = require(serviceAccountPath); -} - -if (serviceAccount) { - try { - admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: - "https://matatu-eta-default-rtdb.europe-west1.firebasedatabase.app/", - }); - console.log("✅ Firebase Admin SDK initialized successfully."); - firebaseInitialized = true; - } catch (error) { - console.error("❌ Error initializing Firebase Admin SDK:", error); - } -} else { - console.warn( - "⚠️ Firebase credentials not found. Checked FIREBASE_CREDENTIALS_BASE64 env var and 'server/config/firebase-service-account.json' file.", - ); - console.warn( - "Firebase-dependent features (live locations, ETAs) will be disabled.", - ); -} - -const db = firebaseInitialized ? admin.database() : null; -const locationsRef = firebaseInitialized ? db.ref("locations") : null; -const etasRef = firebaseInitialized ? db.ref("etas") : null; - -/** - * Fetches the real-time location data for a specific vehicle. - * @param {string} carId - The ID of the car (e.g., "CAR_001"). - * @returns {Promise} A promise that resolves to the vehicle's location data or null if not found. - */ -const getVehicleLocation = async (carId) => { - if (!firebaseInitialized) return null; - const snapshot = await locationsRef.child(carId).once("value"); - return snapshot.val(); -}; - -/** - * Updates the ETA data for a specific stage in Firebase. - * @param {string} stageId - The ID of the stage. - * @param {object} etas - The ETA data object to be stored. - * @returns {Promise} - */ -const updateStageEtas = async (stageId, etas) => { - if (!firebaseInitialized) return; - await etasRef.child(stageId).set(etas); -}; - -/** - * Fetches all vehicle locations from Firebase in a single operation. - * @returns {Promise} A promise that resolves to an object of all vehicle locations or null. - */ -const getAllVehicleLocations = async () => { - if (!firebaseInitialized) return null; - const snapshot = await locationsRef.once("value"); - return snapshot.val(); -}; - -module.exports = { - getVehicleLocation, - updateStageEtas, - getAllVehicleLocations, -}; diff --git a/server/services/geolocationService.js b/server/services/geolocationService.js deleted file mode 100644 index fbf9253..0000000 --- a/server/services/geolocationService.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Converts degrees to radians. - * @param {number} degrees - * @returns {number} - */ -function toRadians(degrees) { - return (degrees * Math.PI) / 180; -} - -/** - * Converts radians to degrees. - * @param {number} radians - * @returns {number} - */ -function toDegrees(radians) { - return (radians * 180) / Math.PI; -} - -/** - * Calculates the bearing (direction) from one GPS coordinate to another. - * @param {{latitude: number, longitude: number}} start - The starting coordinate. - * @param {{latitude: number, longitude: number}} end - The ending coordinate. - * @returns {number} The bearing in degrees (0-360). - */ -function calculateBearing(start, end) { - const startLat = toRadians(start.latitude); - const startLng = toRadians(start.longitude); - const destLat = toRadians(end.latitude); - const destLng = toRadians(end.longitude); - - const y = Math.sin(destLng - startLng) * Math.cos(destLat); - const x = - Math.cos(startLat) * Math.sin(destLat) - - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng); - - let brng = Math.atan2(y, x); - brng = toDegrees(brng); - - return (brng + 360) % 360; // Normalize to 0-360 -} - -/** - * Calculates the distance between two GPS coordinates in meters using the Haversine formula. - * @param {number} lat1 Latitude of the first point. - * @param {number} lon1 Longitude of the first point. - * @param {number} lat2 Latitude of the second point. - * @param {number} lon2 Longitude of the second point. - * @returns {number} The distance in meters. - */ -function haversineDistance(lat1, lon1, lat2, lon2) { - const R = 6371e3; // Earth's radius in meters - const φ1 = toRadians(lat1); - const φ2 = toRadians(lat2); - const Δφ = ((lat2 - lat1) * Math.PI) / 180; - const Δλ = ((lon2 - lon1) * Math.PI) / 180; - - const a = - Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + - Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return R * c; -} - -module.exports = { calculateBearing, haversineDistance }; diff --git a/server/services/googleMapsService.js b/server/services/googleMapsService.js deleted file mode 100644 index 748b354..0000000 --- a/server/services/googleMapsService.js +++ /dev/null @@ -1,117 +0,0 @@ -const axios = require("axios"); -require("dotenv").config(); - -const API_KEY = process.env.GOOGLE_MAPS_API_KEY; -const DIRECTIONS_URL = "https://maps.googleapis.com/maps/api/directions/json"; - -const routeCache = new Map(); -const geocodeCache = new Map(); -const CACHE_DURATION_MS = 5 * 60 * 1000; // Cache results for 5 minutes - -/** - * Calculates the estimated travel time and distance between an origin and a destination. - * @param {object} origin - The starting location { latitude, longitude }. - * @param {object} destination - The ending location { latitude, longitude }. - * @returns {Promise} A promise that resolves to the route leg object, or null on error. - */ -const getDirectionsInfo = async (origin, destination) => { - if (!API_KEY) { - console.error("❌ Google Maps API Key is missing."); - return null; - } - - // Use rounded coordinates for cache key to increase hit rate for minor GPS drifts - const cacheKey = `${origin.latitude.toFixed(3)},${origin.longitude.toFixed(3)}-${destination.latitude.toFixed(3)},${destination.longitude.toFixed(3)}`; - const cached = routeCache.get(cacheKey); - if (cached && cached.expiresAt > Date.now()) { - return cached.data; - } - routeCache.delete(cacheKey); // Clear stale entry - - const params = { - origin: `${origin.latitude},${origin.longitude}`, - destination: `${destination.latitude},${destination.longitude}`, - departure_time: "now", // Add this to get traffic-aware ETAs - key: API_KEY, - }; - - try { - const response = await axios.get(DIRECTIONS_URL, { params, timeout: 5000 }); - const route = response.data.routes[0]; - if (route && route.legs && route.legs[0]) { - const leg = route.legs[0]; - routeCache.set(cacheKey, { - data: leg, - expiresAt: Date.now() + CACHE_DURATION_MS, - }); - return leg; // e.g., { distance: { text, value }, duration: { text, value }, ... } - } - return null; - } catch (error) { - console.error( - "❌ Error fetching directions from Google Maps:", - error.response ? error.response.data : error.message, - ); - return null; - } -}; - -/** - * Converts coordinates into a human-readable address. - * @param {object} location - The location to geocode { latitude, longitude }. - * @returns {Promise} A promise that resolves to a formatted address string or null on error. - */ -const getLocationName = async (location) => { - if (!API_KEY) { - console.error("❌ Google Maps API Key is missing."); - return "Unknown Location"; - } - - // Use rounded coordinates for cache key - const cacheKey = `${location.latitude.toFixed(3)},${location.longitude.toFixed(3)}`; - const cached = geocodeCache.get(cacheKey); - if (cached && cached.expiresAt > Date.now()) { - return cached.data; - } - geocodeCache.delete(cacheKey); // Clear stale entry - - const GEOCODE_URL = "https://maps.googleapis.com/maps/api/geocode/json"; - const params = { - latlng: `${location.latitude},${location.longitude}`, - key: API_KEY, - // Optional: Add location_type to filter results. ROOFTOP, RANGE_INTERPOLATED, GEOMETRIC_CENTER, APPROXIMATE - // Optional: Add result_type to filter. e.g., 'route' to prefer roads. - }; - - try { - const response = await axios.get(GEOCODE_URL, { params, timeout: 5000 }); - if (response.data.results && response.data.results.length > 0) { - const results = response.data.results; - - // --- New Logic to find the best address --- - // 1. Look for a specific street name ('route'). - const route = results.find((r) => r.types.includes("route")); - let address; - if (route) { - address = route.formatted_address; - } else { - // 2. If no street is found, fall back to the most specific result (the first one). - address = results[0].formatted_address; - } - geocodeCache.set(cacheKey, { - data: address, - expiresAt: Date.now() + CACHE_DURATION_MS, - }); - return address; - } - return "Location not found"; - } catch (error) { - console.error( - "❌ Error fetching geocode data from Google Maps:", - error.response ? error.response.data : error.message, - ); - return "Unknown Location"; - } -}; - -module.exports = { getDirectionsInfo, getLocationName }; diff --git a/server/services/locationCache.js b/server/services/locationCache.js deleted file mode 100644 index e8b5ec1..0000000 --- a/server/services/locationCache.js +++ /dev/null @@ -1,61 +0,0 @@ -require("dotenv").config(); - -let redis; -if (process.env.REDIS_URL) { - try { - const Redis = require("ioredis"); - redis = new Redis(process.env.REDIS_URL); - console.log("✅ Redis connected successfully."); - redis.on("error", (err) => - console.error("❌ Redis connection error:", err), - ); - } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { - console.warn( - "⚠️ `ioredis` package not found. Caching service will be in-memory only.", - ); - console.warn("Run `npm install ioredis` to enable Redis caching."); - redis = null; - } else { - // For other errors, it's better to let the app crash. - throw e; - } - } -} else { - console.warn( - "REDIS_URL not set. Caching service will be in-memory only and not scalable.", - ); -} -// This service will now handle the vehicle locations cache. -// It uses Redis if available, otherwise falls back to in-memory Map. -const inMemoryCache = new Map(); -const CACHE_KEY = "vehicle_locations"; -const CACHE_TTL_SECONDS = 60; // How long to keep the data in Redis - -const getLocations = async () => { - if (redis) { - const data = await redis.get(CACHE_KEY); - return data ? new Map(JSON.parse(data)) : new Map(); - } - return inMemoryCache; -}; - -const setLocations = async (locations) => { - const mapAsArray = Array.from(locations.entries()); - if (redis) { - await redis.set( - CACHE_KEY, - JSON.stringify(mapAsArray), - "EX", - CACHE_TTL_SECONDS, - ); - } else { - // For in-memory, we just replace the map. - inMemoryCache.clear(); - for (const [key, value] of locations) { - inMemoryCache.set(key, value); - } - } -}; - -module.exports = { getLocations, setLocations, redis }; diff --git a/server/services/smsService.js b/server/services/smsService.js deleted file mode 100644 index 5d675ba..0000000 --- a/server/services/smsService.js +++ /dev/null @@ -1,42 +0,0 @@ -require("dotenv").config(); -const AfricasTalking = require("africastalking"); - -const credentials = { - apiKey: process.env.AFRICAS_TALKING_API_KEY, - username: process.env.AFRICAS_TALKING_USERNAME, -}; - -// Check for credentials early and initialize the SDK. -let sms; -if (credentials.apiKey && credentials.username) { - const at = AfricasTalking(credentials); - sms = at.SMS; -} else { - console.warn( - "Africa's Talking credentials not set. SMS service is disabled.", - ); -} - -const sendSms = async (phoneNumber, message) => { - // If the SDK was not initialized, skip sending. - if (!sms) { - console.log("Skipping SMS: Africa's Talking service is not configured."); - return { status: "skipped" }; - } - - try { - const response = await sms.send({ - to: [phoneNumber], // The SDK expects an array of numbers - message: message, - }); - - console.log("SMS sent successfully:", response); - return response; - } catch (error) { - // The SDK provides a more structured error object - console.error(`Error sending SMS: ${error.toString()}`); - throw error; - } -}; - -module.exports = { sendSms }; diff --git a/server/services/tokenBlocklistService.js b/server/services/tokenBlocklistService.js deleted file mode 100644 index 238b5cd..0000000 --- a/server/services/tokenBlocklistService.js +++ /dev/null @@ -1,81 +0,0 @@ -require("dotenv").config(); - -let redis; -// In-memory fallback for the blocklist -const inMemoryBlocklist = new Map(); // Use a Map to store jti -> expiration -let cleanupInterval = null; - -if (process.env.REDIS_URL) { - try { - const Redis = require("ioredis"); - redis = new Redis(process.env.REDIS_URL); - console.log("✅ Token blocklist connected to Redis."); - } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { - console.warn( - "⚠️ `ioredis` package not found. Token blocklist will be in-memory only.", - ); - redis = null; - } else { - throw e; - } - } -} else { - console.warn( - "REDIS_URL not set. Token blocklist will be in-memory only and not scalable.", - ); - - // Start a single cleanup interval for the in-memory blocklist - if (!cleanupInterval) { - cleanupInterval = setInterval( - () => { - const now = Math.floor(Date.now() / 1000); - let deletedCount = 0; - for (const [jti, expires] of inMemoryBlocklist.entries()) { - if (expires < now) { - inMemoryBlocklist.delete(jti); - deletedCount++; - } - } - if (deletedCount > 0 && process.env.NODE_ENV !== "production") { - console.log( - `In-memory blocklist cleaned up ${deletedCount} expired tokens. Size: ${inMemoryBlocklist.size}`, - ); - } - }, - 15 * 60 * 1000, - ); // Run every 15 minutes - cleanupInterval.unref(); // Allow the process to exit if this is the only timer - } -} - -const BLOCKLIST_PREFIX = "blocklist:"; - -const addToBlocklist = async (jti, expires) => { - // Calculate the remaining time-to-live for the token in seconds. - const now = Math.floor(Date.now() / 1000); - const ttl = expires - now; - - // Only add to blocklist if the token hasn't already expired. - if (ttl <= 0) { - return; - } - - if (redis) { - await redis.set(`${BLOCKLIST_PREFIX}${jti}`, "1", "EX", ttl); - } else { - // Fallback to in-memory Map, storing the expiration time - inMemoryBlocklist.set(jti, expires); - } -}; - -const isBlocklisted = async (jti) => { - if (redis) { - const result = await redis.get(`${BLOCKLIST_PREFIX}${jti}`); - return result === "1"; - } - // Fallback to in-memory Set - return inMemoryBlocklist.has(jti); -}; - -module.exports = { addToBlocklist, isBlocklisted }; diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..dae634b --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,132 @@ +"use client"; + +import Link from "next/link"; +import { useCallback, useEffect, useState } from "react"; +import "@/styles/pages/admin.css"; +import { ADMIN_LINKS, DashboardHeader } from "@/components/dashboard-header"; +import { Icon } from "@/components/icon"; +import { LiveVehicleTable } from "@/components/live-vehicle-table"; +import { api } from "@/lib/client/api"; +import { usePolling, useProfile, useRoleGuard } from "@/lib/client/hooks"; +import type { FeedbackItem, LiveVehicleRow, Page, Stage } from "@/lib/client/types"; + +const REFRESH_MS = 60_000; + +export default function AdminDashboardPage() { + const ready = useRoleGuard("Admin"); + const profile = useProfile(ready); + const [vehicles, setVehicles] = useState(null); + const [vehiclesError, setVehiclesError] = useState(false); + const [stageCount, setStageCount] = useState(null); + const [feedback, setFeedback] = useState(null); + const [feedbackError, setFeedbackError] = useState(false); + + const loadDashboard = useCallback(async () => { + try { + const [data, stages] = await Promise.all([ + api<{ vehicles: LiveVehicleRow[] }>("/api/admin/dashboard-data"), + api("/api/stages"), + ]); + setVehicles(data.vehicles); + setStageCount(stages.length); + setVehiclesError(false); + } catch { + setVehiclesError(true); + } + }, []); + + usePolling(loadDashboard, REFRESH_MS, ready); + + useEffect(() => { + if (!ready) return; + api>("/api/admin/feedback?limit=10") + .then((r) => setFeedback(r.feedback)) + .catch(() => setFeedbackError(true)); + }, [ready]); + + const online = vehicles?.filter((v) => v.status === "Online").length; + + return ( +
    + Admin Dashboard · MatConnect +
    + + +
    +
    +

    Admin Dashboard

    +

    Welcome back! Here's an overview of the system.

    +
    + +
    + + + +
    + +
    +
    +
    +
    +

    Live Vehicle Status

    + + Manage + +
    + +
    +
    + + +
    +
    +
    +
    + ); +} + +function Stat({ icon, tone, label, value }: { icon: string; tone: string; label: string; value?: number }) { + return ( +
    +
    + +
    +
    +

    {label}

    + {value ?? "--"} +
    +
    + ); +} diff --git a/src/app/admin/stages/page.tsx b/src/app/admin/stages/page.tsx new file mode 100644 index 0000000..71c99e8 --- /dev/null +++ b/src/app/admin/stages/page.tsx @@ -0,0 +1,361 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState, type FormEvent } from "react"; +import "@/styles/pages/admin-stages.css"; +import { ADMIN_LINKS, DashboardHeader } from "@/components/dashboard-header"; +import { Icon } from "@/components/icon"; +import { api, errorMessage } from "@/lib/client/api"; +import { useProfile, useRoleGuard } from "@/lib/client/hooks"; +import type { Page, StageWithVehicles, VehicleSummary } from "@/lib/client/types"; + +type MatAdmin = { id: number; name: string }; + +export default function ManageStagesPage() { + const ready = useRoleGuard("Admin"); + const profile = useProfile(ready); + + const [stages, setStages] = useState(null); + const [vehicles, setVehicles] = useState([]); + const [matAdmins, setMatAdmins] = useState([]); + const [loadError, setLoadError] = useState(null); + const [selectedId, setSelectedId] = useState(null); + const [assignId, setAssignId] = useState(""); + + const [stageModal, setStageModal] = useState(false); + const [stageForm, setStageForm] = useState({ name: "", latitude: "", longitude: "" }); + const [vehicleModal, setVehicleModal] = useState(false); + const [vehicleForm, setVehicleForm] = useState({ plateNumber: "", carId: "", ownerId: "" }); + + const load = useCallback( + () => + Promise.all([ + api>("/api/admin/stages?limit=100"), + api>("/api/admin/vehicles?limit=100"), + api("/api/admin/mat-admins"), + ]).then( + ([s, v, m]) => { + setStages(s.stages); + setVehicles(v.vehicles); + setMatAdmins(m); + setLoadError(null); + }, + (err: unknown) => setLoadError(errorMessage(err)), + ), + [], + ); + + useEffect(() => { + if (ready) load(); + }, [ready, load]); + + const selected = stages?.find((s) => s.id === selectedId) ?? null; + const assignable = useMemo(() => { + const assigned = new Set(selected?.vehicles.map((v) => v.id)); + return vehicles.filter((v) => !assigned.has(v.id)); + }, [selected, vehicles]); + + const patchStage = (id: number, update: (s: StageWithVehicles) => StageWithVehicles) => + setStages((list) => list?.map((s) => (s.id === id ? update(s) : s)) ?? null); + + async function addStage(e: FormEvent) { + e.preventDefault(); + try { + const created = await api("/api/admin/stages", { + method: "POST", + body: { name: stageForm.name, latitude: Number(stageForm.latitude), longitude: Number(stageForm.longitude) }, + }); + setStages((list) => [...(list ?? []), created].sort((a, b) => a.name.localeCompare(b.name))); + setSelectedId(created.id); + setStageModal(false); + setStageForm({ name: "", latitude: "", longitude: "" }); + } catch (err) { + alert(`Error creating stage: ${errorMessage(err)}`); + } + } + + async function addVehicle(e: FormEvent) { + e.preventDefault(); + try { + const created = await api("/api/admin/vehicles", { + method: "POST", + body: { + plateNumber: vehicleForm.plateNumber, + carId: vehicleForm.carId, + ownerId: vehicleForm.ownerId ? Number(vehicleForm.ownerId) : null, + }, + }); + setVehicles((list) => [...list, created].sort((a, b) => a.plateNumber.localeCompare(b.plateNumber))); + setVehicleModal(false); + setVehicleForm({ plateNumber: "", carId: "", ownerId: "" }); + } catch (err) { + alert(`Error creating vehicle: ${errorMessage(err)}`); + } + } + + async function assign() { + if (!selected || !assignId) return; + const vehicle = vehicles.find((v) => v.id === Number(assignId)); + try { + await api("/api/admin/stages/assign-vehicle", { + method: "POST", + body: { stageId: selected.id, vehicleId: Number(assignId) }, + }); + if (vehicle) patchStage(selected.id, (s) => ({ ...s, vehicles: [...s.vehicles, vehicle] })); + setAssignId(""); + } catch (err) { + alert(`Error assigning vehicle: ${errorMessage(err)}`); + } + } + + async function unassign(vehicleId: number) { + if (!selected) return; + try { + await api(`/api/admin/stages/${selected.id}/vehicles/${vehicleId}`, { method: "DELETE" }); + patchStage(selected.id, (s) => ({ ...s, vehicles: s.vehicles.filter((v) => v.id !== vehicleId) })); + } catch (err) { + alert(`Error removing vehicle: ${errorMessage(err)}`); + } + } + + async function deleteVehicle(vehicle: VehicleSummary) { + if (!confirm(`Delete vehicle ${vehicle.plateNumber}? It will be removed from every stage.`)) return; + try { + await api(`/api/admin/vehicles/${vehicle.id}`, { method: "DELETE" }); + await load(); + } catch (err) { + alert(`Error deleting vehicle: ${errorMessage(err)}`); + } + } + + return ( +
    + Manage Stages · MatConnect +
    + + +
    +
    +
    +

    Stages

    +
    +
    +
      + {loadError ? ( +

      Failed to load data: {loadError}

      + ) : !stages ? ( +

      Loading stages...

      + ) : stages.length === 0 ? ( +

      No stages found. Add one to get started.

      + ) : ( + stages.map((stage) => ( +
    • setSelectedId(stage.id)} + onKeyDown={(e) => e.key === "Enter" && setSelectedId(stage.id)} + tabIndex={0} + > +
      + +
      +

      {stage.name}

      + +
    • + )) + )} +
    +
    +
    + +
    +
    + +
    +
    +

    {selected?.name ?? "Select a Stage"}

    +

    + {selected ? ( + <> + Lat: {selected.latitude}, Long: {selected.longitude} + + ) : ( + "Details will appear here." + )} +

    +
    +
    +

    Assigned Vehicles

    +
      + {!selected ? ( +

      Select a stage to see assigned vehicles.

      + ) : selected.vehicles.length === 0 ? ( +

      No vehicles assigned to this stage.

      + ) : ( + selected.vehicles.map((vehicle) => ( +
    • +
      + +

      {vehicle.plateNumber}

      +
      +
      + + +
      +
    • + )) + )} +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + {vehicleModal && ( + setVehicleModal(false)}> +
    +
    +
    + + setVehicleForm({ ...vehicleForm, plateNumber: e.target.value })} + /> +
    +
    + + setVehicleForm({ ...vehicleForm, carId: e.target.value })} + /> +
    +
    + + +
    +
    +
    + + +
    +
    +
    + )} + + {stageModal && ( + setStageModal(false)}> +
    +
    +
    + + setStageForm({ ...stageForm, name: e.target.value })} + /> +
    +
    +
    + + setStageForm({ ...stageForm, latitude: e.target.value })} + /> +
    +
    + + setStageForm({ ...stageForm, longitude: e.target.value })} + /> +
    +
    +
    +
    + + +
    +
    +
    + )} +
    + ); +} + +function Modal({ title, onClose, children }: { title: string; onClose: () => void; children: React.ReactNode }) { + return ( +
    e.target === e.currentTarget && onClose()}> +
    +
    +

    {title}

    + +
    + {children} +
    +
    + ); +} diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx new file mode 100644 index 0000000..5afee5f --- /dev/null +++ b/src/app/admin/users/page.tsx @@ -0,0 +1,273 @@ +"use client"; + +import { useCallback, useEffect, useState, type FormEvent } from "react"; +import "@/styles/pages/admin-users.css"; +import { ADMIN_LINKS, DashboardHeader } from "@/components/dashboard-header"; +import { Icon } from "@/components/icon"; +import { api, errorMessage } from "@/lib/client/api"; +import { useProfile, useRoleGuard } from "@/lib/client/hooks"; +import type { Page, Role, UserRow } from "@/lib/client/types"; + +const ROLE_FILTERS: { label: string; role: Role | "" }[] = [ + { label: "All", role: "" }, + { label: "Admins", role: "Admin" }, + { label: "MatAdmins", role: "MatAdmin" }, + { label: "Passengers", role: "Passenger" }, +]; + +const emptyForm = { name: "", email: "", password: "", role: "MatAdmin" as "MatAdmin" | "Admin" }; + +export default function ManageUsersPage() { + const ready = useRoleGuard("Admin"); + const profile = useProfile(ready); + + const [query, setQuery] = useState(""); + const [debounced, setDebounced] = useState(""); + const [role, setRole] = useState(""); + const [users, setUsers] = useState(null); + const [total, setTotal] = useState(0); + const [error, setError] = useState(false); + + const [modalOpen, setModalOpen] = useState(false); + const [form, setForm] = useState(emptyForm); + const [showPassword, setShowPassword] = useState(false); + const [saving, setSaving] = useState(false); + + useEffect(() => { + const t = setTimeout(() => setDebounced(query), 300); + return () => clearTimeout(t); + }, [query]); + + const fetchUsers = useCallback(() => { + const params = new URLSearchParams({ q: debounced, limit: "100" }); + if (role) params.set("role", role); + return api>(`/api/admin/users?${params}`); + }, [debounced, role]); + + const apply = (res: Page<"users", UserRow>) => { + setUsers(res.users); + setTotal(res.total); + setError(false); + }; + + const load = async () => { + try { + apply(await fetchUsers()); + } catch { + setError(true); + } + }; + + useEffect(() => { + if (!ready) return; + fetchUsers() + .then(apply) + .catch(() => setError(true)); + }, [ready, fetchUsers]); + + async function toggleBlock(user: UserRow) { + const block = !user.disabled; + if (!confirm(`Are you sure you want to ${block ? "block" : "unblock"} ${user.name}?`)) return; + try { + await api(`/api/admin/users/${user.id}`, { method: "PUT", body: { disabled: block } }); + await load(); + } catch (err) { + alert(`Error updating user: ${errorMessage(err)}`); + } + } + + async function remove(user: UserRow) { + if (!confirm(`Permanently delete ${user.name}? This action cannot be undone.`)) return; + try { + await api(`/api/admin/users/${user.id}`, { method: "DELETE" }); + setUsers((list) => list?.filter((u) => u.id !== user.id) ?? null); + setTotal((t) => t - 1); + } catch (err) { + alert(`Error deleting user: ${errorMessage(err)}`); + } + } + + async function createUser(e: FormEvent) { + e.preventDefault(); + setSaving(true); + try { + await api("/api/admin/users", { method: "POST", body: form }); + closeModal(); + await load(); + } catch (err) { + alert(`Error creating user: ${errorMessage(err)}`); + } finally { + setSaving(false); + } + } + + const closeModal = () => { + setModalOpen(false); + setForm(emptyForm); + setShowPassword(false); + }; + + return ( +
    + Manage Users · MatConnect +
    + + +
    +
    +
    +

    System Users{users ? ` (${total})` : ""}

    +
    +
    + + setQuery(e.target.value)} + aria-label="Search users" + /> +
    +
    + {ROLE_FILTERS.map((f) => ( + + ))} +
    + +
    +
    +
    + + + + + + + + + + + + {error ? ( + + + + ) : !users ? ( + + + + ) : users.length === 0 ? ( + + + + ) : ( + users.map((user) => ( + + + + + + + + )) + )} + +
    NameEmailRoleStatusActions
    + Could not load users. +
    Loading users...
    No users found.
    {user.name}{user.email} + {user.role} + + + {user.disabled ? "Blocked" : "Active"} + + + + +
    +
    +
    +
    +
    + + {modalOpen && ( +
    e.target === e.currentTarget && closeModal()}> +
    +
    +

    Add New User

    + +
    +
    +
    +
    + + setForm({ ...form, name: e.target.value })} /> +
    +
    + + setForm({ ...form, email: e.target.value })} + /> +
    +
    + +
    + setForm({ ...form, password: e.target.value })} + /> + +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    + )} +
    + ); +} diff --git a/src/app/api/admin/_shared.ts b/src/app/api/admin/_shared.ts new file mode 100644 index 0000000..c9a05a0 --- /dev/null +++ b/src/app/api/admin/_shared.ts @@ -0,0 +1,5 @@ +import { rateLimits } from "@/server/http/rate-limit"; + +/** Every /api/admin route: Admin role + the admin rate limit (as in the Express router). */ +export const adminOnly = { auth: "Admin", rateLimit: rateLimits.admin } as const; +export const adminDashboard = { auth: "Admin", rateLimit: [rateLimits.admin, rateLimits.dashboard] } as const; diff --git a/src/app/api/admin/dashboard-data/route.ts b/src/app/api/admin/dashboard-data/route.ts new file mode 100644 index 0000000..16bcda7 --- /dev/null +++ b/src/app/api/admin/dashboard-data/route.ts @@ -0,0 +1,5 @@ +import { route } from "@/server/http/route"; +import { dashboardData } from "@/server/modules/admin/admin.service"; +import { adminDashboard } from "../_shared"; + +export const GET = route(adminDashboard, () => dashboardData()); diff --git a/src/app/api/admin/feedback/route.ts b/src/app/api/admin/feedback/route.ts new file mode 100644 index 0000000..132afb5 --- /dev/null +++ b/src/app/api/admin/feedback/route.ts @@ -0,0 +1,6 @@ +import { parseQuery, route } from "@/server/http/route"; +import { feedbackQuerySchema } from "@/server/modules/admin/admin.schemas"; +import { listFeedback } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../_shared"; + +export const GET = route(adminOnly, ({ req }) => listFeedback(parseQuery(req, feedbackQuerySchema))); diff --git a/src/app/api/admin/mat-admins/route.ts b/src/app/api/admin/mat-admins/route.ts new file mode 100644 index 0000000..bd86635 --- /dev/null +++ b/src/app/api/admin/mat-admins/route.ts @@ -0,0 +1,5 @@ +import { route } from "@/server/http/route"; +import { listMatAdmins } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../_shared"; + +export const GET = route(adminOnly, () => listMatAdmins()); diff --git a/src/app/api/admin/stages/[stageId]/vehicles/[vehicleId]/route.ts b/src/app/api/admin/stages/[stageId]/vehicles/[vehicleId]/route.ts new file mode 100644 index 0000000..98f4665 --- /dev/null +++ b/src/app/api/admin/stages/[stageId]/vehicles/[vehicleId]/route.ts @@ -0,0 +1,9 @@ +import { parseParams, route } from "@/server/http/route"; +import { stageVehicleParamsSchema } from "@/server/modules/admin/admin.schemas"; +import { removeVehicleFromStage } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../../../../_shared"; + +export const DELETE = route<{ stageId: string; vehicleId: string }>(adminOnly, ({ params }) => { + const { stageId, vehicleId } = parseParams(params, stageVehicleParamsSchema); + return removeVehicleFromStage(stageId, vehicleId); +}); diff --git a/src/app/api/admin/stages/assign-vehicle/route.ts b/src/app/api/admin/stages/assign-vehicle/route.ts new file mode 100644 index 0000000..eaf2073 --- /dev/null +++ b/src/app/api/admin/stages/assign-vehicle/route.ts @@ -0,0 +1,9 @@ +import { parseBody, route } from "@/server/http/route"; +import { assignVehicleSchema } from "@/server/modules/admin/admin.schemas"; +import { assignVehicleToStage } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../../_shared"; + +export const POST = route(adminOnly, async ({ req }) => { + const { stageId, vehicleId } = await parseBody(req, assignVehicleSchema); + return assignVehicleToStage(stageId, vehicleId); +}); diff --git a/src/app/api/admin/stages/route.ts b/src/app/api/admin/stages/route.ts new file mode 100644 index 0000000..36f45d4 --- /dev/null +++ b/src/app/api/admin/stages/route.ts @@ -0,0 +1,8 @@ +import { json, parseBody, parseQuery, route } from "@/server/http/route"; +import { createStageSchema, paginationQuerySchema } from "@/server/modules/admin/admin.schemas"; +import { createStage, listStagesWithVehicles } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../_shared"; + +export const GET = route(adminOnly, ({ req }) => listStagesWithVehicles(parseQuery(req, paginationQuerySchema))); + +export const POST = route(adminOnly, async ({ req }) => json(await createStage(await parseBody(req, createStageSchema)), 201)); diff --git a/src/app/api/admin/users/[id]/route.ts b/src/app/api/admin/users/[id]/route.ts new file mode 100644 index 0000000..d034d56 --- /dev/null +++ b/src/app/api/admin/users/[id]/route.ts @@ -0,0 +1,17 @@ +import { parseBody, parseParams, route } from "@/server/http/route"; +import { idParamSchema, updateUserSchema } from "@/server/modules/admin/admin.schemas"; +import { deleteUser, setUserDisabled } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../../_shared"; + +type Params = { id: string }; + +/** Block / unblock. */ +export const PUT = route(adminOnly, async ({ req, params, user }) => { + const { id } = parseParams(params, idParamSchema); + const { disabled } = await parseBody(req, updateUserSchema); + return setUserDisabled(user.id, id, disabled); +}); + +export const DELETE = route(adminOnly, ({ params, user }) => + deleteUser(user.id, parseParams(params, idParamSchema).id), +); diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts new file mode 100644 index 0000000..810d92f --- /dev/null +++ b/src/app/api/admin/users/route.ts @@ -0,0 +1,8 @@ +import { json, parseBody, parseQuery, route } from "@/server/http/route"; +import { createUserSchema, listUsersQuerySchema } from "@/server/modules/admin/admin.schemas"; +import { createUser, listUsers } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../_shared"; + +export const GET = route(adminOnly, ({ req, user }) => listUsers(user.id, parseQuery(req, listUsersQuerySchema))); + +export const POST = route(adminOnly, async ({ req }) => json(await createUser(await parseBody(req, createUserSchema)), 201)); diff --git a/src/app/api/admin/vehicles/[id]/route.ts b/src/app/api/admin/vehicles/[id]/route.ts new file mode 100644 index 0000000..60f47ab --- /dev/null +++ b/src/app/api/admin/vehicles/[id]/route.ts @@ -0,0 +1,6 @@ +import { parseParams, route } from "@/server/http/route"; +import { idParamSchema } from "@/server/modules/admin/admin.schemas"; +import { deleteVehicle } from "@/server/modules/admin/admin.service"; +import { adminOnly } from "../../_shared"; + +export const DELETE = route<{ id: string }>(adminOnly, ({ params }) => deleteVehicle(parseParams(params, idParamSchema).id)); diff --git a/src/app/api/admin/vehicles/route.ts b/src/app/api/admin/vehicles/route.ts new file mode 100644 index 0000000..3c20e19 --- /dev/null +++ b/src/app/api/admin/vehicles/route.ts @@ -0,0 +1,9 @@ +import { json, parseBody, parseQuery, route } from "@/server/http/route"; +import { createVehicleSchema, paginationQuerySchema } from "@/server/modules/admin/admin.schemas"; +import { listVehicles } from "@/server/modules/admin/admin.service"; +import { createVehicle } from "@/server/modules/vehicles/vehicles"; +import { adminOnly } from "../_shared"; + +export const GET = route(adminOnly, ({ req }) => listVehicles(parseQuery(req, paginationQuerySchema))); + +export const POST = route(adminOnly, async ({ req }) => json(await createVehicle(await parseBody(req, createVehicleSchema)), 201)); diff --git a/src/app/api/auth/2fa/disable/route.ts b/src/app/api/auth/2fa/disable/route.ts new file mode 100644 index 0000000..97aebbe --- /dev/null +++ b/src/app/api/auth/2fa/disable/route.ts @@ -0,0 +1,8 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { disableTwoFactor } from "@/server/modules/auth/auth.service"; + +export const POST = route({ auth: "any", rateLimit: rateLimits.auth }, async ({ user }) => { + await disableTwoFactor(user.id); + return { message: "2FA disabled successfully" }; +}); diff --git a/src/app/api/auth/2fa/login/route.ts b/src/app/api/auth/2fa/login/route.ts new file mode 100644 index 0000000..a825d7e --- /dev/null +++ b/src/app/api/auth/2fa/login/route.ts @@ -0,0 +1,13 @@ +import { setRefreshCookie } from "@/server/auth/cookies"; +import { rateLimits } from "@/server/http/rate-limit"; +import { json, parseBody, route } from "@/server/http/route"; +import { twoFactorLoginSchema } from "@/server/modules/auth/auth.schemas"; +import { completeTwoFactorLogin } from "@/server/modules/auth/auth.service"; + +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const { tempToken, token } = await parseBody(req, twoFactorLoginSchema); + const session = await completeTwoFactorLogin(tempToken, token); + const res = json({ message: "Login successful", accessToken: session.accessToken, role: session.role }); + setRefreshCookie(res, session.refreshToken); + return res; +}); diff --git a/src/app/api/auth/2fa/setup/route.ts b/src/app/api/auth/2fa/setup/route.ts new file mode 100644 index 0000000..f09fa89 --- /dev/null +++ b/src/app/api/auth/2fa/setup/route.ts @@ -0,0 +1,5 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { beginTwoFactorSetup } from "@/server/modules/auth/auth.service"; + +export const POST = route({ auth: "any", rateLimit: rateLimits.auth }, ({ user }) => beginTwoFactorSetup(user.id)); diff --git a/src/app/api/auth/2fa/verify/route.ts b/src/app/api/auth/2fa/verify/route.ts new file mode 100644 index 0000000..38c3565 --- /dev/null +++ b/src/app/api/auth/2fa/verify/route.ts @@ -0,0 +1,10 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseBody, route } from "@/server/http/route"; +import { verifyTwoFactorSchema } from "@/server/modules/auth/auth.schemas"; +import { confirmTwoFactorSetup } from "@/server/modules/auth/auth.service"; + +export const POST = route({ auth: "any", rateLimit: rateLimits.auth }, async ({ req, user }) => { + const { token } = await parseBody(req, verifyTwoFactorSchema); + await confirmTwoFactorSetup(user.id, token); + return { message: "2FA enabled successfully" }; +}); diff --git a/src/app/api/auth/google/callback/route.ts b/src/app/api/auth/google/callback/route.ts new file mode 100644 index 0000000..e29ab9f --- /dev/null +++ b/src/app/api/auth/google/callback/route.ts @@ -0,0 +1,35 @@ +import { NextResponse } from "next/server"; +import { setRefreshCookie } from "@/server/auth/cookies"; +import { OAUTH_STATE_COOKIE, exchangeGoogleCode } from "@/server/auth/google"; +import { HttpError } from "@/server/http/errors"; +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { loginWithGoogle } from "@/server/modules/auth/auth.service"; + +export const GET = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const failure = (reason = "auth_failed") => { + const res = NextResponse.redirect(new URL(`/login?error=${reason}`, req.url)); + res.cookies.delete({ name: OAUTH_STATE_COOKIE, path: "/api/auth/google" }); + return res; + }; + + const code = req.nextUrl.searchParams.get("code"); + const state = req.nextUrl.searchParams.get("state"); + const expectedState = req.cookies.get(OAUTH_STATE_COOKIE)?.value; + if (!code || !state || !expectedState || state !== expectedState) return failure(); + + try { + const session = await loginWithGoogle(await exchangeGoogleCode(code)); + // The token travels in the fragment, which browsers never send to servers or put in logs. + const target = new URL("/auth/success", req.url); + target.hash = new URLSearchParams({ accessToken: session.accessToken, role: session.role }).toString(); + const res = NextResponse.redirect(target); + res.cookies.delete({ name: OAUTH_STATE_COOKIE, path: "/api/auth/google" }); + setRefreshCookie(res, session.refreshToken); + return res; + } catch (error) { + if (error instanceof HttpError && error.status === 403) return failure("account_disabled"); + console.error("[auth] Google sign-in failed", error); + return failure(); + } +}); diff --git a/src/app/api/auth/google/route.ts b/src/app/api/auth/google/route.ts new file mode 100644 index 0000000..92aedb9 --- /dev/null +++ b/src/app/api/auth/google/route.ts @@ -0,0 +1,21 @@ +import { NextResponse } from "next/server"; +import { OAUTH_STATE_COOKIE, buildGoogleAuthUrl, googleCredentials } from "@/server/auth/google"; +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; + +/** Starts Google sign-in. A random `state` cookie protects the callback against CSRF. */ +export const GET = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const creds = googleCredentials(); + if (!creds) return NextResponse.redirect(new URL("/login?error=google_unavailable", req.url)); + + const state = crypto.randomUUID(); + const res = NextResponse.redirect(buildGoogleAuthUrl(creds.clientId, creds.redirectUri, state)); + res.cookies.set(OAUTH_STATE_COOKIE, state, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/api/auth/google", + maxAge: 600, + }); + return res; +}); diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..fc14c99 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,15 @@ +import { setRefreshCookie } from "@/server/auth/cookies"; +import { rateLimits } from "@/server/http/rate-limit"; +import { json, parseBody, route } from "@/server/http/route"; +import { loginSchema } from "@/server/modules/auth/auth.schemas"; +import { login } from "@/server/modules/auth/auth.service"; + +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const result = await login(await parseBody(req, loginSchema)); + if (result.twoFactorRequired) { + return { twoFactorRequired: true, tempToken: result.tempToken }; + } + const res = json({ message: "Login successful", accessToken: result.accessToken, role: result.role }); + setRefreshCookie(res, result.refreshToken); + return res; +}); diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..8fe8f5e --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,17 @@ +import { REFRESH_COOKIE, clearRefreshCookie } from "@/server/auth/cookies"; +import { rateLimits } from "@/server/http/rate-limit"; +import { json, route } from "@/server/http/route"; +import { logout } from "@/server/modules/auth/auth.service"; + +/** + * Revokes the presented access + refresh tokens and clears the cookie. Works with an expired + * access token too, so a user can always log out. + */ +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const header = req.headers.get("authorization"); + const accessToken = header?.startsWith("Bearer ") ? header.slice(7) : undefined; + await logout(accessToken, req.cookies.get(REFRESH_COOKIE)?.value); + const res = json({ message: "Logged out successfully." }); + clearRefreshCookie(res); + return res; +}); diff --git a/src/app/api/auth/me/admin/route.ts b/src/app/api/auth/me/admin/route.ts new file mode 100644 index 0000000..dc6d659 --- /dev/null +++ b/src/app/api/auth/me/admin/route.ts @@ -0,0 +1,6 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { getProfile } from "@/server/modules/auth/auth.service"; + +/** Kept for API compatibility: profile, but only for Admins. Prefer GET /api/auth/me. */ +export const GET = route({ auth: "Admin", rateLimit: rateLimits.auth }, ({ user }) => getProfile(user.id)); diff --git a/src/app/api/auth/me/mat-admin/route.ts b/src/app/api/auth/me/mat-admin/route.ts new file mode 100644 index 0000000..a98025a --- /dev/null +++ b/src/app/api/auth/me/mat-admin/route.ts @@ -0,0 +1,6 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { getProfile } from "@/server/modules/auth/auth.service"; + +/** Kept for API compatibility: profile, but only for Matatu admins. Prefer GET /api/auth/me. */ +export const GET = route({ auth: "MatAdmin", rateLimit: rateLimits.auth }, ({ user }) => getProfile(user.id)); diff --git a/src/app/api/auth/me/passenger/route.ts b/src/app/api/auth/me/passenger/route.ts new file mode 100644 index 0000000..d8d0384 --- /dev/null +++ b/src/app/api/auth/me/passenger/route.ts @@ -0,0 +1,6 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { getProfile } from "@/server/modules/auth/auth.service"; + +/** Kept for API compatibility: profile, but only for Passengers. Prefer GET /api/auth/me. */ +export const GET = route({ auth: "Passenger", rateLimit: rateLimits.auth }, ({ user }) => getProfile(user.id)); diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts new file mode 100644 index 0000000..f2e7735 --- /dev/null +++ b/src/app/api/auth/me/route.ts @@ -0,0 +1,12 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseBody, route } from "@/server/http/route"; +import { updateProfileSchema } from "@/server/modules/auth/auth.schemas"; +import { getProfile, updateProfile } from "@/server/modules/auth/auth.service"; + +/** Current user's profile (any role). */ +export const GET = route({ auth: "any", rateLimit: rateLimits.auth }, ({ user }) => getProfile(user.id)); + +export const PUT = route({ auth: "any", rateLimit: rateLimits.auth }, async ({ req, user }) => { + await updateProfile(user.id, await parseBody(req, updateProfileSchema)); + return { message: "Profile updated successfully" }; +}); diff --git a/src/app/api/auth/refresh-token/route.ts b/src/app/api/auth/refresh-token/route.ts new file mode 100644 index 0000000..cecdb56 --- /dev/null +++ b/src/app/api/auth/refresh-token/route.ts @@ -0,0 +1,14 @@ +import { REFRESH_COOKIE, setRefreshCookie } from "@/server/auth/cookies"; +import { rateLimits } from "@/server/http/rate-limit"; +import { json, route } from "@/server/http/route"; +import { refreshSession } from "@/server/modules/auth/auth.service"; + +/** Rotates the refresh cookie and returns a new access token. */ +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const header = req.headers.get("authorization"); + const accessToken = header?.startsWith("Bearer ") ? header.slice(7) : undefined; + const session = await refreshSession(req.cookies.get(REFRESH_COOKIE)?.value, accessToken); + const res = json({ accessToken: session.accessToken, role: session.role }); + setRefreshCookie(res, session.refreshToken); + return res; +}); diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts new file mode 100644 index 0000000..d03df1d --- /dev/null +++ b/src/app/api/auth/register/route.ts @@ -0,0 +1,9 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { json, parseBody, route } from "@/server/http/route"; +import { registerSchema } from "@/server/modules/auth/auth.schemas"; +import { register } from "@/server/modules/auth/auth.service"; + +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const user = await register(await parseBody(req, registerSchema)); + return json({ message: "User created successfully", user }, 201); +}); diff --git a/src/app/api/auth/requestPasswordReset/route.ts b/src/app/api/auth/requestPasswordReset/route.ts new file mode 100644 index 0000000..b92afec --- /dev/null +++ b/src/app/api/auth/requestPasswordReset/route.ts @@ -0,0 +1,9 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseBody, route } from "@/server/http/route"; +import { requestPasswordResetSchema } from "@/server/modules/auth/auth.schemas"; +import { requestPasswordReset } from "@/server/modules/auth/auth.service"; + +export const POST = route({ rateLimit: rateLimits.auth }, async ({ req }) => { + const { email } = await parseBody(req, requestPasswordResetSchema); + return { message: await requestPasswordReset(email) }; +}); diff --git a/src/app/api/auth/resetpassword/[id]/[token]/route.ts b/src/app/api/auth/resetpassword/[id]/[token]/route.ts new file mode 100644 index 0000000..c056556 --- /dev/null +++ b/src/app/api/auth/resetpassword/[id]/[token]/route.ts @@ -0,0 +1,11 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseBody, parseParams, route } from "@/server/http/route"; +import { resetPasswordBodySchema, resetPasswordParamsSchema } from "@/server/modules/auth/auth.schemas"; +import { resetPassword } from "@/server/modules/auth/auth.service"; + +export const POST = route<{ id: string; token: string }>({ rateLimit: rateLimits.auth }, async ({ req, params }) => { + const { id, token } = parseParams(params, resetPasswordParamsSchema); + const { password } = await parseBody(req, resetPasswordBodySchema); + await resetPassword(id, token, password); + return { message: "Password has been reset" }; +}); diff --git a/src/app/api/cron/eta-cycle/route.ts b/src/app/api/cron/eta-cycle/route.ts new file mode 100644 index 0000000..29c8df6 --- /dev/null +++ b/src/app/api/cron/eta-cycle/route.ts @@ -0,0 +1,44 @@ +import { timingSafeEqual } from "node:crypto"; +import { env } from "@/server/env"; +import { errorResponse, json } from "@/server/http/route"; +import { runEtaCycle } from "@/server/jobs/eta-cycle"; + +export const dynamic = "force-dynamic"; +export const maxDuration = 60; + +function authorised(header: string | null, secret: string | undefined): boolean { + if (!secret || !header?.startsWith("Bearer ")) return false; + const given = Buffer.from(header.slice(7)); + const expected = Buffer.from(secret); + return given.length === expected.length && timingSafeEqual(given, expected); +} + +const CYCLE_SPACING_MS = 30_000; + +/** + * Runs the ETA cycle. Called by Vercel Cron (GET, `Authorization: Bearer $CRON_SECRET`) or any + * external scheduler. Overlapping calls are harmless: the job lease makes extra runs skip. + * + * Cron schedules have one-minute granularity, so `?runs=2` runs a second cycle 30 s after the first + * to keep the original 30-second cadence from a once-a-minute trigger. + */ +async function handler(req: Request) { + if (!authorised(req.headers.get("authorization"), env().CRON_SECRET)) { + return json({ message: "Unauthorized" }, 401); + } + const runs = Math.min(Math.max(Number(new URL(req.url).searchParams.get("runs")) || 1, 1), 2); + try { + const startedAt = Date.now(); + const reports = [await runEtaCycle()]; + for (let i = 1; i < runs; i++) { + const wait = startedAt + i * CYCLE_SPACING_MS - Date.now(); + if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait)); + reports.push(await runEtaCycle()); + } + return json({ runs: reports }); + } catch (error) { + return errorResponse(error); + } +} + +export { handler as GET, handler as POST }; diff --git a/src/app/api/eta/[stageId]/route.ts b/src/app/api/eta/[stageId]/route.ts new file mode 100644 index 0000000..a1df23e --- /dev/null +++ b/src/app/api/eta/[stageId]/route.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { rateLimits } from "@/server/http/rate-limit"; +import { parseParams, route } from "@/server/http/route"; +import { getStageEta } from "@/server/modules/eta/eta.service"; +import { id } from "@/server/validation/common"; + +const paramsSchema = z.object({ stageId: id("Stage ID") }); + +/** Live ETAs for the vehicles serving a stage. */ +export const GET = route<{ stageId: string }>({ auth: "any", rateLimit: rateLimits.api }, ({ params }) => + getStageEta(parseParams(params, paramsSchema).stageId), +); diff --git a/src/app/api/feedback/route.ts b/src/app/api/feedback/route.ts new file mode 100644 index 0000000..44ea652 --- /dev/null +++ b/src/app/api/feedback/route.ts @@ -0,0 +1,7 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { json, parseBody, route } from "@/server/http/route"; +import { submitFeedback, submitFeedbackSchema } from "@/server/modules/feedback/feedback.service"; + +export const POST = route({ auth: "Passenger", rateLimit: rateLimits.api }, async ({ req, user }) => + json(await submitFeedback(user.id, await parseBody(req, submitFeedbackSchema)), 201), +); diff --git a/src/app/api/feedback/vehicles/route.ts b/src/app/api/feedback/vehicles/route.ts new file mode 100644 index 0000000..c29ee26 --- /dev/null +++ b/src/app/api/feedback/vehicles/route.ts @@ -0,0 +1,5 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { listVehiclesForFeedback } from "@/server/modules/feedback/feedback.service"; + +export const GET = route({ auth: "Passenger", rateLimit: rateLimits.api }, () => listVehiclesForFeedback()); diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..66537e3 --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,15 @@ +import { db } from "@/server/db"; +import { json } from "@/server/http/route"; + +export const dynamic = "force-dynamic"; + +/** Liveness + database reachability, for uptime checks and deploy smoke tests. */ +export async function GET() { + try { + await db.$queryRaw`SELECT 1`; + return json({ status: "ok", database: "ok" }); + } catch (error) { + console.error("[health] database check failed", error); + return json({ status: "degraded", database: "unreachable" }, 503); + } +} diff --git a/src/app/api/mat-admin/_shared.ts b/src/app/api/mat-admin/_shared.ts new file mode 100644 index 0000000..e0ddb77 --- /dev/null +++ b/src/app/api/mat-admin/_shared.ts @@ -0,0 +1,4 @@ +import { rateLimits } from "@/server/http/rate-limit"; + +export const matAdminOnly = { auth: "MatAdmin", rateLimit: rateLimits.admin } as const; +export const matAdminDashboard = { auth: "MatAdmin", rateLimit: [rateLimits.admin, rateLimits.dashboard] } as const; diff --git a/src/app/api/mat-admin/dashboard-data/route.ts b/src/app/api/mat-admin/dashboard-data/route.ts new file mode 100644 index 0000000..f2cbe9c --- /dev/null +++ b/src/app/api/mat-admin/dashboard-data/route.ts @@ -0,0 +1,5 @@ +import { route } from "@/server/http/route"; +import { dashboardData } from "@/server/modules/mat-admin/mat-admin.service"; +import { matAdminDashboard } from "../_shared"; + +export const GET = route(matAdminDashboard, ({ user }) => dashboardData(user.id)); diff --git a/src/app/api/mat-admin/dashboard-summary/route.ts b/src/app/api/mat-admin/dashboard-summary/route.ts new file mode 100644 index 0000000..aa9d6b8 --- /dev/null +++ b/src/app/api/mat-admin/dashboard-summary/route.ts @@ -0,0 +1,5 @@ +import { route } from "@/server/http/route"; +import { dashboardSummary } from "@/server/modules/mat-admin/mat-admin.service"; +import { matAdminDashboard } from "../_shared"; + +export const GET = route(matAdminDashboard, ({ user }) => dashboardSummary(user.id)); diff --git a/src/app/api/mat-admin/fleet-aggregate-data/route.ts b/src/app/api/mat-admin/fleet-aggregate-data/route.ts new file mode 100644 index 0000000..49f2493 --- /dev/null +++ b/src/app/api/mat-admin/fleet-aggregate-data/route.ts @@ -0,0 +1,5 @@ +import { parseQuery, route } from "@/server/http/route"; +import { fleetAggregate, periodQuerySchema } from "@/server/modules/mat-admin/mat-admin.service"; +import { matAdminOnly } from "../_shared"; + +export const GET = route(matAdminOnly, ({ req, user }) => fleetAggregate(user.id, parseQuery(req, periodQuerySchema).period)); diff --git a/src/app/api/mat-admin/vehicle-stats/route.ts b/src/app/api/mat-admin/vehicle-stats/route.ts new file mode 100644 index 0000000..083de1a --- /dev/null +++ b/src/app/api/mat-admin/vehicle-stats/route.ts @@ -0,0 +1,6 @@ +import { parseQuery, route } from "@/server/http/route"; +import { statsQuerySchema, vehicleStats } from "@/server/modules/mat-admin/mat-admin.service"; +import { matAdminOnly } from "../_shared"; + +/** ?date=YYYY-MM-DD (UTC) — defaults to today. */ +export const GET = route(matAdminOnly, ({ req, user }) => vehicleStats(user.id, parseQuery(req, statsQuerySchema).date)); diff --git a/src/app/api/mat-admin/vehicles/[vehicleId]/chart-data/route.ts b/src/app/api/mat-admin/vehicles/[vehicleId]/chart-data/route.ts new file mode 100644 index 0000000..eb96bc8 --- /dev/null +++ b/src/app/api/mat-admin/vehicles/[vehicleId]/chart-data/route.ts @@ -0,0 +1,7 @@ +import { parseParams, parseQuery, route } from "@/server/http/route"; +import { periodQuerySchema, vehicleChartData, vehicleIdParamSchema } from "@/server/modules/mat-admin/mat-admin.service"; +import { matAdminOnly } from "../../../_shared"; + +export const GET = route<{ vehicleId: string }>(matAdminOnly, ({ req, params, user }) => + vehicleChartData(user.id, parseParams(params, vehicleIdParamSchema).vehicleId, parseQuery(req, periodQuerySchema).period), +); diff --git a/src/app/api/mat-admin/vehicles/route.ts b/src/app/api/mat-admin/vehicles/route.ts new file mode 100644 index 0000000..f48dc08 --- /dev/null +++ b/src/app/api/mat-admin/vehicles/route.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; +import { json, parseBody, route } from "@/server/http/route"; +import { createVehicleSchema } from "@/server/modules/admin/admin.schemas"; +import { myVehicles } from "@/server/modules/mat-admin/mat-admin.service"; +import { createVehicle } from "@/server/modules/vehicles/vehicles"; +import { matAdminOnly } from "../_shared"; + +const bodySchema = createVehicleSchema.pick({ carId: true, plateNumber: true }); + +export const GET = route(matAdminOnly, ({ user }) => myVehicles(user.id)); + +/** A Matatu admin's new vehicle is always owned by them. */ +export const POST = route(matAdminOnly, async ({ req, user }) => { + const input: z.infer = await parseBody(req, bodySchema); + return json(await createVehicle({ ...input, ownerId: user.id }), 201); +}); diff --git a/src/app/api/stages/[stageId]/subscribe/route.ts b/src/app/api/stages/[stageId]/subscribe/route.ts new file mode 100644 index 0000000..b1b2ae8 --- /dev/null +++ b/src/app/api/stages/[stageId]/subscribe/route.ts @@ -0,0 +1,8 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseParams, route } from "@/server/http/route"; +import { subscribe } from "@/server/modules/stages/stages.service"; +import { stageParamsSchema, type StageParams } from "../../params"; + +export const POST = route({ auth: "any", rateLimit: rateLimits.subscription }, ({ params, user }) => + subscribe(user.id, parseParams(params, stageParamsSchema).stageId), +); diff --git a/src/app/api/stages/[stageId]/subscription/route.ts b/src/app/api/stages/[stageId]/subscription/route.ts new file mode 100644 index 0000000..3e2ba34 --- /dev/null +++ b/src/app/api/stages/[stageId]/subscription/route.ts @@ -0,0 +1,8 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseParams, route } from "@/server/http/route"; +import { getSubscriptionStatus } from "@/server/modules/stages/stages.service"; +import { stageParamsSchema, type StageParams } from "../../params"; + +export const GET = route({ auth: "any", rateLimit: rateLimits.api }, ({ params, user }) => + getSubscriptionStatus(user.id, parseParams(params, stageParamsSchema).stageId), +); diff --git a/src/app/api/stages/[stageId]/unsubscribe/route.ts b/src/app/api/stages/[stageId]/unsubscribe/route.ts new file mode 100644 index 0000000..cbc599b --- /dev/null +++ b/src/app/api/stages/[stageId]/unsubscribe/route.ts @@ -0,0 +1,8 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { parseParams, route } from "@/server/http/route"; +import { unsubscribe } from "@/server/modules/stages/stages.service"; +import { stageParamsSchema, type StageParams } from "../../params"; + +export const POST = route({ auth: "any", rateLimit: rateLimits.subscription }, ({ params, user }) => + unsubscribe(user.id, parseParams(params, stageParamsSchema).stageId), +); diff --git a/src/app/api/stages/params.ts b/src/app/api/stages/params.ts new file mode 100644 index 0000000..0f1fe0e --- /dev/null +++ b/src/app/api/stages/params.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; +import { id } from "@/server/validation/common"; + +export const stageParamsSchema = z.object({ stageId: id("Stage ID") }); +export type StageParams = { stageId: string }; diff --git a/src/app/api/stages/route.ts b/src/app/api/stages/route.ts new file mode 100644 index 0000000..5a81ef8 --- /dev/null +++ b/src/app/api/stages/route.ts @@ -0,0 +1,5 @@ +import { rateLimits } from "@/server/http/rate-limit"; +import { route } from "@/server/http/route"; +import { listStages } from "@/server/modules/stages/stages.service"; + +export const GET = route({ auth: "any", rateLimit: rateLimits.api }, () => listStages()); diff --git a/src/app/auth/success/page.tsx b/src/app/auth/success/page.tsx new file mode 100644 index 0000000..6883084 --- /dev/null +++ b/src/app/auth/success/page.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useEffect } from "react"; +import { homePathFor, setSession } from "@/lib/client/session"; +import type { Role } from "@/lib/client/types"; + +const ROLES: Role[] = ["Passenger", "Admin", "MatAdmin"]; + +/** + * Landing page after Google sign-in. The server puts the access token in the URL *fragment* + * (#accessToken=…&role=…). The old page read the query string instead, so this flow was broken. + */ +export default function AuthSuccessPage() { + useEffect(() => { + const params = new URLSearchParams(window.location.hash.slice(1)); + const accessToken = params.get("accessToken"); + const role = params.get("role") as Role | null; + // Remove the token from the address bar / history straight away. + window.history.replaceState(null, "", window.location.pathname); + + if (accessToken && role && ROLES.includes(role)) { + setSession(accessToken, role); + window.location.replace(homePathFor(role)); + } else { + window.location.replace("/login?error=auth_failed"); + } + }, []); + + return ( +
    + Signing in… · MatConnect +

    Please wait, you are being redirected…

    +
    + ); +} diff --git a/src/app/feedback/page.tsx b/src/app/feedback/page.tsx new file mode 100644 index 0000000..81e4d12 --- /dev/null +++ b/src/app/feedback/page.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { Fragment, useEffect, useState, type FormEvent } from "react"; +import "@/styles/pages/feedback.css"; +import { PassengerHeader } from "@/components/passenger-header"; +import { api, errorMessage } from "@/lib/client/api"; +import { useProfile, useRoleGuard } from "@/lib/client/hooks"; + +type VehicleOption = { id: number; plateNumber: string }; + +export default function FeedbackPage() { + const ready = useRoleGuard("Passenger"); + const profile = useProfile(ready); + const [vehicles, setVehicles] = useState(null); + const [loadError, setLoadError] = useState(false); + const [vehicleId, setVehicleId] = useState(""); + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(""); + const [busy, setBusy] = useState(false); + const [notice, setNotice] = useState<{ text: string; ok: boolean } | null>(null); + + useEffect(() => { + if (!ready) return; + api("/api/feedback/vehicles") + .then(setVehicles) + .catch(() => setLoadError(true)); + }, [ready]); + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + if (!vehicleId) return setNotice({ text: "Please select a vehicle.", ok: false }); + if (!rating) return setNotice({ text: "Please provide a star rating.", ok: false }); + setBusy(true); + try { + const res = await api<{ message: string }>("/api/feedback", { + method: "POST", + body: { vehicleId: Number(vehicleId), rating, comment }, + }); + setNotice({ text: res.message, ok: true }); + setVehicleId(""); + setRating(0); + setComment(""); + } catch (error) { + setNotice({ text: `Error submitting feedback: ${errorMessage(error)}`, ok: false }); + } finally { + setBusy(false); + } + } + + return ( +
    + Feedback · MatConnect +
    + + +
    +
    +
    +

    Your Feedback Matters

    +

    Help us improve your matatu experience. Share your thoughts on your recent ride.

    +
    +
    +
    + + +
    +
    + + {/* Visual order 1→5 comes from `flex-direction: row-reverse` in the stylesheet. */} +
    + {[5, 4, 3, 2, 1].map((value) => ( + // Inputs and labels must stay direct siblings for the stylesheet's `label:hover ~ label`. + + setRating(value)} + /> + + + ))} +
    +
    +
    + +