diff --git a/README.md b/README.md index 33f22dc0a..0a47b6a90 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,620 @@ -# EDR Passenger API +# EDR Platform - Ethio-Djibouti Railway Passenger API -NestJS REST API for the Ethio-Djibouti Railway passenger platform. Handles booking lifecycle, seat inventory, payments (Telebirr, CBE Birr, eBirr, Card, Wallet), loyalty, live tracking, notifications, and support. +Enterprise-grade NestJS REST API for the Ethio-Djibouti Railway passenger booking and management platform. Built with TypeScript, PostgreSQL, and Prisma ORM. -## Tech Stack +## ๐Ÿš€ Features -- **Runtime**: Node.js 20, TypeScript -- **Framework**: NestJS 11 -- **Database**: PostgreSQL via Prisma ORM -- **Auth**: JWT (Passport) -- **Docs**: Swagger / OpenAPI (`/api-docs`) -- **Package manager**: pnpm 9 +### ๐Ÿ†• NEW: Age-Based Pricing, Verifayda 2.0 & Multi-Currency -## Prerequisites +#### Age-Based Pricing +- **ADULT** (โ‰ฅ5 years): Pay 100% of base fare +- **CHILD** (<5 years): First child travels FREE, subsequent children pay 100% +- Automatic age calculation from date of birth +- Example: 2 adults + 3 children = 4ร— base fare (first child free) -- Node.js >= 20 -- pnpm >= 9 (`npm i -g pnpm`) -- PostgreSQL 15+ +#### Verifayda 2.0 Integration +- Real-time Ethiopian national ID verification +- Retrieves passenger data from government database +- National IDs NOT stored (policy compliant) +- Non-Ethiopians use passport (no verification required) +- Booking fails if verification unsuccessful -## Quick Start +#### Multi-Currency Support +- **Transaction Currency**: ETB (Ethiopian Birr) +- **Display Currencies**: ETB, DJF (Djiboutian Franc), USD (US Dollar) +- Real-time exchange rate conversion +- Prices shown in user's preferred currency +- Exchange rates: ETBโ†’DJF=3.25, ETBโ†’USD=0.018 +### Core Modules +- **Authentication & Authorization** - Dual authentication system: + - **Passenger Auth**: JWT-based auth with OTP verification, password reset, account lockout + - **Corporate IAM**: Integration with @tria-plc corporate identity system for back-office operations (agents, supervisors, admins) + - Role-based access control (RBAC) with granular permissions +- **Age-Based Pricing** - Smart passenger categorization: + - **ADULT** (โ‰ฅ5 years): Full fare + - **CHILD** (<5 years): First child free, subsequent children full fare + - Automatic age calculation from date of birth +- **Verifayda 2.0 Integration** - Ethiopian national ID verification: + - Real-time verification via government API + - Retrieves passenger data (name, DOB, nationality) + - National IDs NOT stored (policy compliant) + - Non-Ethiopians use passport (no verification) +- **Multi-Currency Support** - Display prices in multiple currencies: + - **ETB** (Ethiopian Birr) - Transaction currency + - **DJF** (Djiboutian Franc) - Display option + - **USD** (US Dollar) - Display option + - Real-time exchange rate conversion +- **Booking Management** - Complete booking lifecycle with modification, cancellation, refunds, and fare breakdown +- **Payment Integration** - Multi-provider support (Telebirr, CBE Birr, eBirr, Card, Wallet) with webhook handling +- **Seat Management** - Real-time seat inventory, holds, releases, and blocking with coach/class management +- **Ticketing** - QR code and barcode generation, PDF tickets, gate validation with audit logs +- **Agent Operations** - Counter booking, shift management, commission tracking, and reconciliation +- **Passenger Services** - Profile management, traveler profiles, saved routes, and preferences +- **Loyalty Program** - Points accumulation, tier management (Bronze/Silver/Gold/Platinum), and rewards +- **Wallet System** - Balance management, top-up, transaction ledger +- **Live Tracking** - Real-time trip status, location updates, delay notifications, crowd signals +- **Notifications** - Multi-channel (Email, SMS, Push) with templating engine +- **Support System** - FAQ management, live chat conversations +- **Reports & Analytics** - Revenue reports, occupancy analytics, agent sales tracking +- **Route Management** - Route configuration, stops, fare rules, baggage allowance + +### Technical Features +- **Security** - Password hashing (bcrypt), JWT tokens, rate limiting, audit logging +- **Validation** - Request validation with class-validator, DTO transformation +- **Documentation** - Auto-generated Swagger/OpenAPI docs at `/api-docs` +- **Error Handling** - Global exception filters with standardized error responses +- **Database** - PostgreSQL with Prisma ORM, migrations, and comprehensive seeding +- **Scheduling** - Cron jobs for automated tasks (seat release, report generation) +- **Event System** - Event-driven architecture with @nestjs/event-emitter + +## ๐Ÿ“‹ Prerequisites + +- **Node.js** >= 20.x +- **pnpm** >= 9.x (`npm install -g pnpm`) +- **PostgreSQL** >= 15.x +- **Git** + +## ๐Ÿ› ๏ธ Installation & Setup + +### 1. Clone Repository ```bash -# 1. Install dependencies (from monorepo root) -pnpm install +git clone +cd edr-platform +``` -# 2. Copy and fill environment variables +### 2. Install Dependencies +```bash +pnpm install +``` + +### 3. Environment Configuration +```bash +# Copy environment template cp apps/edr-passenger-api/.env.example apps/edr-passenger-api/.env -# 3. Generate Prisma client +# Edit .env file with your configuration +``` + +#### Required Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `NODE_ENV` | Environment mode | `development` | +| `PORT` | HTTP server port | `4000` | +| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/edr_passenger` | +| `JWT_SECRET` | JWT signing secret (change in production) | `your-secret-key` | +| `JWT_EXPIRES_IN` | JWT token expiry | `7d` | +| `FRONTEND_URL` | Web app CORS origin | `http://localhost:3000` | +| `PORTAL_URL` | Admin portal CORS origin | `http://localhost:3001` | +| `SENDGRID_API_KEY` | SendGrid API key (optional) | `SG.xxx` | +| `SENDGRID_FROM_EMAIL` | Email sender address | `noreply@edr-platform.com` | + +#### Verifayda 2.0 Configuration (Ethiopian National ID Verification) + +| Variable | Description | Example | +|----------|-------------|---------| +| `VERIFAYDA_ENABLED` | Enable Verifayda integration | `true` or `false` | +| `VERIFAYDA_API_URL` | Verifayda API endpoint | `https://api.verifayda.gov.et/v2` | +| `VERIFAYDA_API_KEY` | API key for Verifayda service | `your-verifayda-api-key` | + +**Note:** When `VERIFAYDA_ENABLED=false`, verification is skipped (development mode only). + +#### Corporate IAM Configuration (Back-office Authentication) + +| Variable | Description | Example | +|----------|-------------|---------| +| `IAM_ENABLED` | Enable corporate IAM integration | `true` or `false` | +| `IAM_API_URL` | Corporate IAM API endpoint | `https://iam.tria-plc.com/api` | +| `IAM_API_KEY` | API key for IAM service | `your-iam-api-key` | + +**Note:** When `IAM_ENABLED=false`, IAM-protected routes allow access without validation (development mode only). + +#### Optional: Payment Provider Configuration +```bash +# Telebirr Configuration +TELEBIRR_BASE_URL=https://api.telebirr.com +TELEBIRR_MERCHANT_CODE=your-merchant-code +TELEBIRR_APP_SECRET=your-app-secret +# ... see .env.example for complete list +``` + +### 4. Database Setup + +#### Start PostgreSQL +```bash +# Using Docker (recommended) +docker run --name edr-postgres \ + -e POSTGRES_USER=edr \ + -e POSTGRES_PASSWORD=edr_secret \ + -e POSTGRES_DB=edr_passenger \ + -p 5432:5432 \ + -d postgres:15 + +# Or use your local PostgreSQL installation +``` + +#### Generate Prisma Client +```bash pnpm --filter @edr/passenger-api run prisma:generate +``` -# 4. Run database migrations +#### Run Migrations +```bash pnpm --filter @edr/passenger-api run prisma:migrate +``` -# 5. Seed the database +#### Seed Database +```bash pnpm --filter @edr/passenger-api run prisma:seed +``` -# 6. Start in development mode +**Seed Data Includes:** +- 21 Stations (Complete Ethiopian-Djibouti Railway with country codes) +- 1 Route with 21 stops and fare rules +- 2 Train services with 4 trips +- 360 seats across 12 coaches (Economy, Bed, VIP classes) +- 3 User accounts (Admin, Passenger, Agent) +- Fare rules for ADULT and CHILD passenger categories +- Currency exchange rates (ETB, DJF, USD) +- Baggage allowance rules +- Notification templates +- Promotions and FAQ content +- Menu items and station crowd signals +- Fraud detection rules + +### 5. Start Development Server +```bash pnpm --filter @edr/passenger-api run dev ``` -API runs at **http://localhost:4000** -Swagger UI at **http://localhost:4000/api-docs** +**API Server:** http://localhost:4000 +**Swagger Docs:** http://localhost:4000/api-docs -## Environment Variables +## ๐Ÿ”‘ Default Credentials -| Variable | Description | Default | -| --------------------- | ------------------------------------ | -------------------------------- | -| `PORT` | HTTP port | `4000` | -| `DATABASE_URL` | PostgreSQL connection string | โ€” | -| `JWT_SECRET` | JWT signing secret | โ€” | -| `JWT_EXPIRES_IN` | JWT expiry | `7d` | -| `FRONTEND_URL` | Allowed CORS origin (web app) | `http://localhost:3000` | -| `PORTAL_URL` | Allowed CORS origin (portal) | `http://localhost:3001` | -| `SENDGRID_API_KEY` | SendGrid key for email notifications | _(optional โ€” logs if absent)_ | -| `SENDGRID_FROM_EMAIL` | Sender email address | `noreply@edr-platform.com` | +After seeding, use these credentials to test the API: -## API Modules +| Role | Email | Password | Description | +|------|-------|----------|-------------| +| **Admin** | `admin@edr-platform.com` | `admin123` | Full system access, reports, agent management | +| **Passenger** | `kelemu@email.com` | `password123` | Regular user with loyalty (Silver) and wallet | +| **Agent** | `agent@edr-platform.com` | `agent123` | Counter booking agent with commission tracking | -| Tag | Base path | Description | -| -------------- | ----------------- | ---------------------------------------- | -| Auth | `/auth` | Register, login, JWT | -| Stations | `/stations` | Station directory | -| Fleet | `/fleet` | Train services, coaches, seat batches | -| Schedule | `/schedule` | Trips, fare rules, status updates | -| Search | `/search` | Trip search, fare quotes | -| Seats | `/seats` | Seat maps, holds, releases | -| Booking | `/bookings` | Create, retrieve, cancel bookings | -| Payment | `/payments` | Initiate payment, refunds, methods | -| Tickets | `/tickets` | QR ticket generation and validation | -| Passenger | `/passengers` | Profiles, traveler profiles, saved routes| -| Notifications | `/notifications` | In-app notifications | -| Loyalty | `/loyalty` | Points, tiers, rewards | -| Wallet | `/wallet` | Balance, top-up, ledger | -| Promotions | `/promos` | Active promos, promo code validation | -| Live Tracking | `/live` | Real-time trip status, crowd signals | -| Support | `/support` | FAQ, chat conversations | -| Dashboard | `/dashboard` | Home screen aggregate | +## ๐Ÿ“š API Documentation -## Seed Credentials +### Swagger UI +Interactive API documentation available at: **http://localhost:4000/api-docs** -After running `prisma:seed`: +### Authentication Methods -| Role | Email | Password | -| --------- | ------------------------ | ------------- | -| Passenger | `kelemu@email.com` | `password123` | -| Admin | `admin@edr-platform.com` | `admin123` | +The API uses two authentication schemes: -## Docker +#### 1. JWT Authentication (Passenger-facing) +- **Used for**: Passenger bookings, profile management, wallet, loyalty +- **Header**: `Authorization: Bearer ` +- **Obtain token**: `POST /auth/login` with passenger credentials +- **Swagger Security**: `JWT-auth` + +#### 2. IAM Authentication (Back-office) +- **Used for**: Agent operations, fraud detection, reports, admin functions +- **Header**: `Authorization: Bearer ` +- **Obtain token**: From corporate IAM system (https://iam.tria-plc.com) +- **Swagger Security**: `IAM-auth` +- **Roles**: AGENT, SUPERVISOR, ADMIN, STAFF + +### API Endpoints Overview + +| Module | Base Path | Auth Type | Description | +|--------|-----------|-----------|-------------| +| **Auth** | `/auth` | Public/JWT | Register, login, OTP verification, password reset | +| **Agents** | `/agents` | IAM | Agent booking, shifts, commissions, reconciliation | +| **Fraud Detection** | `/fraud` | IAM | Fraud alerts, rules management, user blocking | +| **Reports** | `/reports` | IAM | Revenue, occupancy, agent sales analytics | +| **Stations** | `/stations` | JWT | Station directory and information | +| **Fleet** | `/fleet` | JWT/IAM | Train services, coaches, seat configurations | +| **Schedules** | `/schedules` | JWT/IAM | Trip schedules, fare rules, status updates | +| **Search** | `/search` | JWT | Trip search, availability, fare quotes | +| **Seats** | `/seats` | JWT/IAM | Seat maps, holds, releases, blocking | +| **Bookings** | `/bookings` | JWT | Create, modify, cancel bookings | +| **Payments** | `/payments` | JWT/Public | Payment initiation, webhooks, refunds | +| **Tickets** | `/tickets` | JWT/IAM | Ticket generation, QR/barcode, validation | +| **Passengers** | `/passengers` | JWT | Profile management, traveler profiles | +| **Notifications** | `/notifications` | JWT | In-app notifications, preferences | +| **Loyalty** | `/loyalty` | JWT | Points, tiers, rewards redemption | +| **Wallet** | `/wallet` | JWT | Balance, top-up, transaction history | +| **Promotions** | `/promos` | JWT | Active promotions, promo code validation | +| **Live Tracking** | `/live` | JWT | Real-time trip status, crowd signals | +| **Support** | `/support` | JWT | FAQ, chat conversations | +| **Dashboard** | `/dashboard` | JWT | Home screen aggregated data | + +### Example API Calls + +#### 1. Register Passenger +```bash +POST /auth/register +Content-Type: application/json + +{ + "email": "user@example.com", + "phone": "+251911234567", + "fullName": "John Doe", + "password": "SecurePass123" +} +``` + +#### 2. Login (Passenger) +```bash +POST /auth/login +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "SecurePass123" +} + +# Response includes JWT token +{ + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { "id": "uuid", "role": "PASSENGER" } +} +``` + +#### 2b. Agent Booking (IAM Auth) +```bash +POST /agents/bookings +Authorization: Bearer +Content-Type: application/json + +{ + "tripId": "uuid", + "seats": [...], + "paymentMethod": "CASH", + "cashReceived": 50000 +} +``` + +#### 3. Search Trips +```bash +GET /search/trips?originStationId={id}&destinationStationId={id}&date=2026-06-15&adultCount=2&childCount=1 +Authorization: Bearer {token} +``` + +#### 4. Get Fare Quote +```bash +POST /search/fare-quote +Authorization: Bearer {token} +Content-Type: application/json + +{ + "tripId": "uuid", + "serviceClass": "ECONOMY_REGULAR", + "adultCount": 2, + "childCount": 1, + "displayCurrency": "USD" +} + +# Response includes age-based pricing breakdown +{ + "baseFareMinor": 35000, + "adultCount": 2, + "adultFareMinor": 70000, + "childCount": 1, + "freeChildrenCount": 1, + "paidChildrenCount": 0, + "childFareMinor": 0, + "totalMinor": 73500, + "currency": "ETB", + "displayCurrency": "USD", + "displayTotalMinor": 1323 +} +``` + +#### 5. Create Booking +```bash +POST /bookings +Authorization: Bearer {token} +Content-Type: application/json + +{ + "tripId": "uuid", + "holdId": "uuid", + "serviceClass": "ECONOMY_REGULAR", + "displayCurrency": "DJF", + "passengers": [ + { + "seatId": "uuid", + "passengerName": "Abebe Kebede", + "dateOfBirth": "1985-03-15", + "idDocumentType": "NATIONAL_ID", + "idDocumentNumber": "ET123456789" + }, + { + "seatId": "uuid", + "passengerName": "Sara Abebe", + "dateOfBirth": "2023-01-10", + "idDocumentType": "NATIONAL_ID", + "idDocumentNumber": "ET987654321" + }, + { + "seatId": "uuid", + "passengerName": "John Smith", + "dateOfBirth": "1990-07-20", + "idDocumentType": "PASSPORT", + "passportNumber": "P1234567", + "passportCountry": "Kenya" + } + ] +} +``` + +## ๐Ÿ—๏ธ Project Structure + +``` +apps/edr-passenger-api/ +โ”œโ”€โ”€ prisma/ +โ”‚ โ”œโ”€โ”€ schema.prisma # Database schema (40+ models) +โ”‚ โ”œโ”€โ”€ seed.ts # Comprehensive seed script +โ”‚ โ””โ”€โ”€ migrations/ # Database migrations +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ common/ # Shared utilities +โ”‚ โ”‚ โ”œโ”€โ”€ filters/ # Exception filters +โ”‚ โ”‚ โ”œโ”€โ”€ interceptors/ # Response interceptors +โ”‚ โ”‚ โ”œโ”€โ”€ pipes/ # Validation pipes +โ”‚ โ”‚ โ”œโ”€โ”€ i18n/ # Internationalization +โ”‚ โ”‚ โ”œโ”€โ”€ jwt.guard.ts # JWT authentication guard (passengers) +โ”‚ โ”‚ โ”œโ”€โ”€ jwt.strategy.ts # Passport JWT strategy +โ”‚ โ”‚ โ”œโ”€โ”€ iam-adapter.ts # Corporate IAM guard (back-office) +โ”‚ โ”‚ โ”œโ”€โ”€ iam.module.ts # IAM module +โ”‚ โ”‚ โ”œโ”€โ”€ roles.guard.ts # RBAC authorization guard +โ”‚ โ”‚ โ”œโ”€โ”€ roles.decorator.ts # Roles decorator +โ”‚ โ”‚ โ”œโ”€โ”€ prisma.service.ts # Prisma client service +โ”‚ โ”‚ โ””โ”€โ”€ prisma.module.ts # Prisma module +โ”‚ โ”œโ”€โ”€ config/ # Configuration files +โ”‚ โ”‚ โ”œโ”€โ”€ app.config.ts # App configuration +โ”‚ โ”‚ โ”œโ”€โ”€ database.config.ts # Database configuration +โ”‚ โ”‚ โ””โ”€โ”€ telebirr.config.ts # Payment provider config +โ”‚ โ”œโ”€โ”€ modules/ # Feature modules +โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # Authentication & authorization (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ agents/ # Agent operations (IAM-protected) +โ”‚ โ”‚ โ”œโ”€โ”€ bookings/ # Booking management (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ currency/ # Currency conversion service +โ”‚ โ”‚ โ”œโ”€โ”€ dashboard/ # Dashboard aggregations (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ fleet/ # Train fleet management (JWT/IAM) +โ”‚ โ”‚ โ”œโ”€โ”€ fraud/ # Fraud detection (IAM-protected) +โ”‚ โ”‚ โ”œโ”€โ”€ live/ # Live tracking (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ loyalty/ # Loyalty program (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ notifications/ # Notification system (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ passengers/ # Passenger management (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ payments/ # Payment processing (JWT/Webhooks) +โ”‚ โ”‚ โ”œโ”€โ”€ promos/ # Promotions (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ reports/ # Reports & analytics (IAM-protected) +โ”‚ โ”‚ โ”œโ”€โ”€ schedules/ # Trip schedules (JWT/IAM) +โ”‚ โ”‚ โ”œโ”€โ”€ search/ # Trip search (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ seats/ # Seat management (JWT/IAM) +โ”‚ โ”‚ โ”œโ”€โ”€ segments/ # Journey segments (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ stations/ # Station management (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ support/ # Customer support (JWT) +โ”‚ โ”‚ โ”œโ”€โ”€ tickets/ # Ticketing (JWT/IAM) +โ”‚ โ”‚ โ”œโ”€โ”€ verifayda/ # Verifayda 2.0 integration +โ”‚ โ”‚ โ””โ”€โ”€ wallet/ # Wallet system (JWT) +โ”‚ โ”œโ”€โ”€ app.module.ts # Root application module +โ”‚ โ””โ”€โ”€ main.ts # Application entry point +โ”œโ”€โ”€ test/ # E2E tests +โ”œโ”€โ”€ .env.example # Environment template +โ”œโ”€โ”€ Dockerfile # Docker configuration +โ”œโ”€โ”€ nest-cli.json # NestJS CLI configuration +โ”œโ”€โ”€ package.json # Dependencies & scripts +โ”œโ”€โ”€ tsconfig.json # TypeScript configuration +โ””โ”€โ”€ tsconfig.build.json # Build configuration +``` + +## ๐Ÿ—„๏ธ Database Schema + +### Key Models (40+ total) + +**Core Entities:** +- `User`, `Session`, `Passenger`, `Agent` +- `Station`, `Route`, `RouteStop`, `RouteFareRule` +- `TrainService`, `Trip`, `TripStopTime`, `Coach`, `Seat` +- `Booking`, `BookingSeat`, `Ticket` +- `PaymentIntent`, `PaymentRefund`, `PaymentWebhookEvent` + +**Enhanced Features:** +- `OtpCode`, `PasswordResetToken` (Auth) +- `AgentBooking`, `AgentShift`, `AgentCommission` (Agents) +- `BookingModification`, `BookingCancellation` (Booking lifecycle) +- `GateValidationLog` (Ticket validation) +- `BaggageAllowance`, `BaggageBooking` (Baggage) +- `LoyaltyAccount`, `LoyaltyLedgerEntry`, `LoyaltyReward` +- `WalletAccount`, `WalletLedgerEntry` +- `Notification`, `NotificationTemplate` +- `AuditLog`, `OperationalReport` +- `SeatBlock`, `SeatHold` +- `CurrencyExchangeRate` (Multi-currency) +- `VerifaydaVerification` (National ID verification) + +## ๐Ÿ”ง Available Scripts ```bash -# Build image (run from monorepo root) +# Development +pnpm --filter @edr/passenger-api run dev # Start with hot-reload + +# Build +pnpm --filter @edr/passenger-api run build # Compile TypeScript + +# Production +pnpm --filter @edr/passenger-api run start # Run compiled code + +# Testing +pnpm --filter @edr/passenger-api run test # Unit tests +pnpm --filter @edr/passenger-api run test:e2e # E2E tests + +# Code Quality +pnpm --filter @edr/passenger-api run lint # ESLint +pnpm --filter @edr/passenger-api run type-check # TypeScript check + +# Database +pnpm --filter @edr/passenger-api run prisma:generate # Generate Prisma client +pnpm --filter @edr/passenger-api run prisma:migrate # Run migrations +pnpm --filter @edr/passenger-api run prisma:seed # Seed database +``` + +## ๐Ÿณ Docker Deployment + +### Build Image +```bash +# From monorepo root docker build -f apps/edr-passenger-api/Dockerfile -t edr-passenger-api . - -# Run -docker run -p 4000:4000 --env-file apps/edr-passenger-api/.env edr-passenger-api ``` -## Scripts - -| Command | Description | -| ---------------------- | ------------------------------ | -| `pnpm dev` | Start with hot-reload | -| `pnpm build` | Compile to `dist/` | -| `pnpm start` | Run compiled output | -| `pnpm test` | Run unit tests | -| `pnpm lint` | ESLint | -| `pnpm type-check` | TypeScript type check | -| `pnpm prisma:generate` | Regenerate Prisma client | -| `pnpm prisma:migrate` | Run pending migrations | -| `pnpm prisma:seed` | Seed the database | - -## Project Structure - +### Run Container +```bash +docker run -d \ + --name edr-api \ + -p 4000:4000 \ + --env-file apps/edr-passenger-api/.env \ + edr-passenger-api ``` -src/ -โ”œโ”€โ”€ common/ # PrismaService, JwtGuard, JwtStrategy, filters, interceptors -โ”œโ”€โ”€ config/ # app.config.ts, database.config.ts -โ””โ”€โ”€ modules/ - โ”œโ”€โ”€ auth/ - โ”œโ”€โ”€ stations/ - โ”œโ”€โ”€ fleet/ - โ”œโ”€โ”€ schedules/ - โ”œโ”€โ”€ search/ - โ”œโ”€โ”€ seats/ - โ”œโ”€โ”€ bookings/ - โ”œโ”€โ”€ payments/ - โ”œโ”€โ”€ tickets/ - โ”œโ”€โ”€ passengers/ - โ”œโ”€โ”€ notifications/ - โ”œโ”€โ”€ loyalty/ - โ”œโ”€โ”€ wallet/ - โ”œโ”€โ”€ promos/ - โ”œโ”€โ”€ live/ - โ”œโ”€โ”€ support/ - โ””โ”€โ”€ dashboard/ -prisma/ -โ”œโ”€โ”€ schema.prisma -โ”œโ”€โ”€ seed.ts -โ””โ”€โ”€ migrations/ + +### Docker Compose (Recommended) +```yaml +version: '3.8' +services: + postgres: + image: postgres:15 + environment: + POSTGRES_USER: edr + POSTGRES_PASSWORD: edr_secret + POSTGRES_DB: edr_passenger + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + api: + build: + context: . + dockerfile: apps/edr-passenger-api/Dockerfile + ports: + - "4000:4000" + environment: + DATABASE_URL: postgresql://edr:edr_secret@postgres:5432/edr_passenger + JWT_SECRET: your-secret-key + PORT: 4000 + depends_on: + - postgres + +volumes: + postgres_data: ``` + +## ๐Ÿ”’ Security Best Practices + +1. **Environment Variables** - Never commit `.env` files. Use secrets management in production. +2. **JWT Secret** - Use strong, randomly generated secrets (min 32 characters). +3. **Password Hashing** - Bcrypt with salt rounds (default: 10). +4. **Rate Limiting** - Implement rate limiting for auth endpoints. +5. **CORS** - Configure allowed origins in production. +6. **HTTPS** - Always use HTTPS in production. +7. **Database** - Use connection pooling and prepared statements (Prisma handles this). +8. **Audit Logging** - All sensitive operations are logged in `AuditLog` table. +9. **Dual Authentication** - Passenger routes use JWT, back-office routes use corporate IAM. +10. **IAM Integration** - Corporate IAM validates tokens against centralized identity service. +11. **Role-Based Access** - Granular permissions enforced via IAM roles (AGENT, SUPERVISOR, ADMIN). +12. **Token Validation** - IAM tokens validated in real-time with 5-second timeout. + +## ๐Ÿ“Š Monitoring & Logging + +- **Application Logs** - NestJS built-in logger +- **Database Queries** - Prisma query logging (enable in development) +- **Audit Trail** - All user actions logged in `AuditLog` table +- **Error Tracking** - Global exception filters with detailed error responses + +## ๐Ÿงช Testing + +```bash +# Unit tests +pnpm --filter @edr/passenger-api run test + +# E2E tests +pnpm --filter @edr/passenger-api run test:e2e + +# Test coverage +pnpm --filter @edr/passenger-api run test:cov +``` + +## ๐Ÿš€ Production Deployment + +### Pre-deployment Checklist +- [ ] Update environment variables (JWT_SECRET, DATABASE_URL, etc.) +- [ ] Configure IAM integration (IAM_ENABLED=true, IAM_API_URL, IAM_API_KEY) +- [ ] Configure Verifayda integration (VERIFAYDA_ENABLED=true, VERIFAYDA_API_KEY) +- [ ] Set up currency exchange rate sync (external API) +- [ ] Set NODE_ENV=production +- [ ] Configure CORS origins (FRONTEND_URL, PORTAL_URL) +- [ ] Set up SSL/TLS certificates +- [ ] Configure database connection pooling +- [ ] Set up monitoring and logging +- [ ] Configure backup strategy +- [ ] Test payment provider integrations +- [ ] Verify IAM token validation endpoint +- [ ] Test Verifayda verification with real national IDs +- [ ] Verify currency conversion accuracy +- [ ] Test age-based pricing calculations +- [ ] Review security settings and audit logs +- [ ] Test both JWT and IAM authentication flows + +### Deployment Steps +```bash +# 1. Build application +pnpm --filter @edr/passenger-api run build + +# 2. Run migrations +pnpm --filter @edr/passenger-api run prisma:migrate + +# 3. Start production server +NODE_ENV=production pnpm --filter @edr/passenger-api run start:prod +``` + +## ๐Ÿค Contributing + +1. Fork the repository +2. Create feature branch (`git checkout -b feature/amazing-feature`) +3. Commit changes (`git commit -m 'Add amazing feature'`) +4. Push to branch (`git push origin feature/amazing-feature`) +5. Open Pull Request + +## ๐Ÿ“ License + +This project is proprietary and confidential. + +## ๐Ÿ“ง Support + +For technical support or questions: +- Email: support@edr-platform.com +- Documentation: http://localhost:4000/api-docs + +--- + +**Built with โค๏ธ for Ethio-Djibouti Railway** diff --git a/apps/edr-passenger-api/.env.example b/apps/edr-passenger-api/.env.example index bee21ed65..d0e64691d 100644 --- a/apps/edr-passenger-api/.env.example +++ b/apps/edr-passenger-api/.env.example @@ -3,7 +3,7 @@ NODE_ENV=development PORT=4000 # Database (Prisma) -DATABASE_URL=postgresql://edr:edr_secret@localhost:5432/edr_passenger +DATABASE_URL=postgresql://edr:edr_secret@localhost:5432/edr_passenger?schema=edr_passenger # CORS FRONTEND_URL=http://localhost:3000 @@ -17,7 +17,72 @@ JWT_EXPIRES_IN=7d SENDGRID_API_KEY= SENDGRID_FROM_EMAIL=noreply@edr-platform.com +# SMS Configuration +SMS_PROVIDER=twilio +SMS_API_KEY= + +# Twilio (if SMS_PROVIDER=twilio) +TWILIO_ACCOUNT_SID= +TWILIO_AUTH_TOKEN= +TWILIO_FROM_NUMBER= + +# Africa's Talking (if SMS_PROVIDER=africastalking) +AFRICASTALKING_USERNAME= +AFRICASTALKING_FROM= + # Telebirr -TELEBIRR_API_URL=https://api.telebirr.com -TELEBIRR_APP_ID= -TELEBIRR_APP_KEY= +TELEBIRR_BASE_URL= +TELEBIRR_WEB_BASE_URL= +TELEBIRR_FABRIC_APP_ID= +TELEBIRR_APP_SECRET= +TELEBIRR_MERCHANT_APP_ID= +TELEBIRR_MERCHANT_CODE= +TELEBIRR_NOTIFY_URL= +TELEBIRR_RETURN_URL= +TELEBIRR_TIMEOUT_EXPRESS=15m +TELEBIRR_PRIVATE_KEY= +TELEBIRR_PUBLIC_KEY= +TELEBIRR_INSECURE_TLS=false + +# CBE Birr +CBE_BASE_URL= +CBE_MERCHANT_ID= +CBE_SECRET_KEY= +CBE_NOTIFY_URL= +CBE_RETURN_URL= + +# eBirr +EBIRR_BASE_URL= +EBIRR_MERCHANT_CODE= +EBIRR_SECRET_KEY= +EBIRR_NOTIFY_URL= +EBIRR_RETURN_URL= + +# Card Gateway (Stripe-like) +CARD_BASE_URL= +CARD_API_KEY= +CARD_WEBHOOK_SECRET= +CARD_WEBHOOK_URL= +CARD_RETURN_URL= + +# Payment Configuration +PAYMENT_PROVIDERS_ENABLED=TELEBIRR,CBE_BIRR,EBIRR,CARD,WALLET + +# Session Configuration +SESSION_INACTIVITY_MINUTES=30 + +# i18n Configuration +DEFAULT_LOCALE=en +SUPPORTED_LOCALES=en,am,fr,om + +# Corporate IAM Configuration (for back-office authentication) +IAM_ENABLED=false +IAM_API_URL=https://iam.tria-plc.com/api +IAM_API_KEY= + +# Verifayda 2.0 Configuration (Ethiopian National ID Verification) +VERIFAYDA_ENABLED=false +VERIFAYDA_API_URL=https://api.verifayda.gov.et/v2 +VERIFAYDA_API_KEY= + +GITHUB_PACKAGE_TOKEN= \ No newline at end of file diff --git a/apps/edr-passenger-api/.npmrc b/apps/edr-passenger-api/.npmrc new file mode 100644 index 000000000..f1a000f80 --- /dev/null +++ b/apps/edr-passenger-api/.npmrc @@ -0,0 +1,6 @@ +# GitHub Packages configuration for @tria-plc scope +@tria-plc:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGE_TOKEN} + +# Default registry for other packages +registry=https://registry.npmjs.org/ diff --git a/apps/edr-passenger-api/REFACTORING_SUMMARY.md b/apps/edr-passenger-api/REFACTORING_SUMMARY.md new file mode 100644 index 000000000..30648fa3f --- /dev/null +++ b/apps/edr-passenger-api/REFACTORING_SUMMARY.md @@ -0,0 +1,199 @@ +# Train Reservation System Refactoring - Complete + +## โœ… Refactoring Summary + +Successfully refactored the train reservation system from an incorrect tight-coupling model to a flexible, realistic railway architecture. + +--- + +## ๐Ÿ”„ Architecture Changes + +### Before (Incorrect) +``` +TrainService โ†’ Trip โ†’ Coach โ†’ Seat +``` +- Coaches were permanently bound to specific trips +- No reusability of physical coaches +- Inflexible train composition + +### After (Correct) +``` +Train โ†’ TrainSchedule โ†” CoachAssignment โ†” Coach โ†’ Seat +``` +- **Train**: Logical service entity (e.g., "Express 301") +- **TrainSchedule**: Specific journey with date/time +- **Coach**: Physical reusable railway carriage +- **CoachAssignment**: Join table linking schedules to coaches +- **Seat**: Belongs strictly to physical coach + +--- + +## ๐Ÿ“‹ Files Modified + +### Schema & Database +- โœ… `prisma/schema.prisma` - Complete entity redesign +- โœ… `prisma/seed.ts` - Rewritten for new architecture + +### DTOs +- โœ… `fleet/fleet.dto.ts` - New Train/Coach/Assignment DTOs +- โœ… `schedules/schedules.dto.ts` - TrainSchedule DTOs +- โœ… `bookings/bookings.dto.ts` - scheduleId instead of tripId + +### Services +- โœ… `fleet/fleet.service.ts` - Physical coach management +- โœ… `fleet/fleet.controller.ts` - New endpoints +- โœ… `schedules/schedules.service.ts` - TrainSchedule operations +- โœ… `schedules/schedules.controller.ts` - Updated routes +- โœ… `bookings/bookings.service.ts` - scheduleId references +- โœ… `seats/seats.service.ts` - CoachAssignment queries +- โœ… `search/search.service.ts` - TrainSchedule search +- โœ… `segments/segments.service.ts` - scheduleId throughout +- โœ… `segments/enhanced-seats.service.ts` - Fixed references +- โœ… `passengers/passengers.service.ts` - schedule.train +- โœ… `live/live.service.ts` - TrainSchedule live tracking +- โœ… `live/live.controller.ts` - scheduleId routes +- โœ… `dashboard/dashboard.service.ts` - schedule references +- โœ… `reports/reports.service.ts` - Occupancy with assignments + +--- + +## ๐Ÿ—„๏ธ Database Schema Changes + +### New Models +```prisma +model Train { + id String @id @default(uuid()) + number String @unique + name String + schedules TrainSchedule[] +} + +model TrainSchedule { + id String @id @default(uuid()) + trainId String + departureAt DateTime + train Train @relation(...) + coachAssignments CoachAssignment[] +} + +model Coach { + id String @id @default(uuid()) + coachNumber String @unique // Physical identifier + label String + seatClassId String + mode String // 'seat', 'bed', 'convertible' + totalUnits Int + seats Seat[] + assignments CoachAssignment[] +} + +model CoachAssignment { + id String @id @default(uuid()) + scheduleId String + coachId String + positionNumber Int + schedule TrainSchedule @relation(...) + coach Coach @relation(...) +} +``` + +### Renamed Models +- `TrainService` โ†’ `Train` +- `Trip` โ†’ `TrainSchedule` +- `TripStopTime.tripId` โ†’ `scheduleId` +- `TripLiveStatus.tripId` โ†’ `scheduleId` +- `Booking.tripId` โ†’ `scheduleId` +- `SeatHold.tripId` โ†’ `scheduleId` +- `MenuItem.tripId` โ†’ `scheduleId` +- `JourneySegment.tripId` โ†’ `scheduleId` + +--- + +## ๐ŸŽฏ Key Benefits + +1. **Reusability**: Physical coaches can be assigned to different schedules +2. **Flexibility**: Train composition can change per schedule +3. **Realistic**: Matches real-world railway operations +4. **Maintainability**: Clear separation of logical vs physical entities +5. **Scalability**: Easy to add/remove coaches from schedules + +--- + +## ๐Ÿš‚ Example Usage + +### Creating a Physical Coach +```typescript +const coach = await prisma.coach.create({ + data: { + coachNumber: 'C-A1', + label: 'A', + seatClassId: economyClassId, + mode: 'seat', + totalUnits: 60, + }, +}); +``` + +### Assigning Coach to Schedule +```typescript +await prisma.coachAssignment.create({ + data: { + scheduleId: schedule1.id, + coachId: coach.id, + positionNumber: 1, + }, +}); +``` + +### Querying Schedule with Coaches +```typescript +const schedule = await prisma.trainSchedule.findUnique({ + where: { id: scheduleId }, + include: { + train: true, + coachAssignments: { + include: { + coach: { + include: { seats: true, seatClass: true }, + }, + }, + orderBy: { positionNumber: 'asc' }, + }, + }, +}); +``` + +--- + +## ๐Ÿ”‘ Seed Data + +- **2 Trains**: Express 301, Express 302 +- **6 Physical Coaches**: C-A1, C-B1, C-C1, C-A2, C-B2, C-C2 +- **4 Train Schedules**: With flexible coach assignments +- **3 Seat Classes**: Economy Regular, Economy Bed, VIP Bed +- **Users**: Admin, Passenger (with wallet/loyalty), Agent + +--- + +## โœจ Migration Status + +โœ… Schema pushed to database successfully +โœ… Seed data populated +โœ… All services updated +โœ… All controllers updated +โœ… All DTOs updated + +--- + +## ๐Ÿ“ Notes + +- Coaches are now reusable physical entities +- Same coach can serve different schedules at different times +- Seats belong to coaches, not schedules +- CoachAssignment provides the many-to-many relationship +- All references to `tripId` changed to `scheduleId` +- All references to `service` changed to `train` + +--- + +**Refactoring completed successfully! ๐ŸŽ‰** diff --git a/apps/edr-passenger-api/package.json b/apps/edr-passenger-api/package.json index aa3f34d64..ae10747f5 100644 --- a/apps/edr-passenger-api/package.json +++ b/apps/edr-passenger-api/package.json @@ -13,12 +13,15 @@ "type-check": "tsc --noEmit", "prisma:generate": "prisma generate", "prisma:migrate": "prisma migrate dev", - "prisma:seed": "ts-node prisma/seed.ts" + "prisma:seed": "ts-node prisma/seed.ts", + "prisma:backfill": "ts-node prisma/backfill-fields.ts", + "prisma:verify": "ts-node prisma/verify-backfill.ts" }, "prisma": { "seed": "ts-node prisma/seed.ts" }, "dependencies": { + "@nestjs/axios": "^4.0.1", "@nestjs/common": "^11.0.0", "@nestjs/config": "^4.0.4", "@nestjs/core": "^11.1.19", @@ -28,8 +31,8 @@ "@nestjs/platform-express": "^11.1.19", "@nestjs/schedule": "^6.1.3", "@nestjs/swagger": "^7.4.0", - "@prisma/client": "^5.8.0", "@sendgrid/mail": "^8.1.0", + "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -47,6 +50,7 @@ "@nestjs/cli": "^11.0.21", "@nestjs/schematics": "^11.1.0", "@nestjs/testing": "^11.1.19", + "@prisma/client": "^6.19.3", "@types/bcrypt": "^5.0.2", "@types/jest": "^29.5.11", "@types/node": "^20.10.6", @@ -54,7 +58,7 @@ "@types/qrcode": "^1.5.5", "@types/supertest": "^6.0.2", "jest": "^29.7.0", - "prisma": "^5.8.0", + "prisma": "^6.19.3", "supertest": "^7.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", diff --git a/apps/edr-passenger-api/prisma/migrations/20260513080558_initial_migration/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260513080558_initial_migration/migration.sql deleted file mode 100644 index 5ba579dca..000000000 --- a/apps/edr-passenger-api/prisma/migrations/20260513080558_initial_migration/migration.sql +++ /dev/null @@ -1,690 +0,0 @@ --- CreateEnum -CREATE TYPE "UserRole" AS ENUM ('PASSENGER', 'ADMIN', 'STAFF'); - --- CreateEnum -CREATE TYPE "TripStatus" AS ENUM ('SCHEDULED', 'BOARDING', 'EN_ROUTE', 'ARRIVED', 'CANCELLED', 'DELAYED'); - --- CreateEnum -CREATE TYPE "SeatKind" AS ENUM ('STANDARD', 'PREMIUM', 'ACCESSIBLE'); - --- CreateEnum -CREATE TYPE "SeatStatus" AS ENUM ('AVAILABLE', 'HELD', 'BOOKED', 'BLOCKED'); - --- CreateEnum -CREATE TYPE "ServiceClass" AS ENUM ('ECONOMY', 'BUSINESS', 'FIRST'); - --- CreateEnum -CREATE TYPE "BookingStatus" AS ENUM ('DRAFT', 'PENDING_PAYMENT', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW'); - --- CreateEnum -CREATE TYPE "PaymentMethodType" AS ENUM ('TELEBIRR', 'CBE_BIRR', 'EBIRR', 'CARD', 'WALLET'); - --- CreateEnum -CREATE TYPE "PaymentIntentStatus" AS ENUM ('REQUIRES_ACTION', 'PROCESSING', 'SUCCEEDED', 'FAILED', 'CANCELLED'); - --- CreateEnum -CREATE TYPE "WalletLedgerType" AS ENUM ('CREDIT', 'DEBIT'); - --- CreateEnum -CREATE TYPE "NotificationCategory" AS ENUM ('BOOKING', 'PAYMENT', 'DISRUPTION', 'PROMOTION', 'SYSTEM'); - --- CreateEnum -CREATE TYPE "StopStatus" AS ENUM ('COMPLETED', 'APPROACHING', 'CURRENT', 'UPCOMING'); - --- CreateEnum -CREATE TYPE "SupportConversationStatus" AS ENUM ('OPEN', 'RESOLVED', 'CLOSED'); - --- CreateEnum -CREATE TYPE "SupportSender" AS ENUM ('USER', 'BOT', 'AGENT'); - --- CreateEnum -CREATE TYPE "LoyaltyTier" AS ENUM ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM'); - --- CreateEnum -CREATE TYPE "LoyaltyLedgerReason" AS ENUM ('TRIP_COMPLETED', 'REWARD_REDEEMED', 'PROMO_BONUS', 'MANUAL_ADJUSTMENT', 'EXPIRY'); - --- CreateEnum -CREATE TYPE "FoodOrderStatus" AS ENUM ('PENDING', 'PREPARING', 'READY', 'DELIVERED', 'CANCELLED'); - --- CreateEnum -CREATE TYPE "DevicePlatform" AS ENUM ('IOS', 'ANDROID', 'WEB'); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "email" TEXT NOT NULL, - "phone" TEXT NOT NULL, - "fullName" TEXT NOT NULL, - "passwordHash" TEXT NOT NULL, - "role" "UserRole" NOT NULL DEFAULT 'PASSENGER', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Session" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "token" TEXT NOT NULL, - "expiresAt" TIMESTAMP(3) NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Session_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Passenger" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Passenger_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "TravelerProfile" ( - "id" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "fullName" TEXT NOT NULL, - "relationship" TEXT NOT NULL, - "dateOfBirth" TIMESTAMP(3), - "nationalId" TEXT, - "notes" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "TravelerProfile_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Station" ( - "id" TEXT NOT NULL, - "code" TEXT NOT NULL, - "name" TEXT NOT NULL, - "city" TEXT NOT NULL, - "timezone" TEXT NOT NULL DEFAULT 'Africa/Addis_Ababa', - "lat" DECIMAL(9,6) NOT NULL, - "lng" DECIMAL(9,6) NOT NULL, - - CONSTRAINT "Station_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "TrainService" ( - "id" TEXT NOT NULL, - "number" TEXT NOT NULL, - "name" TEXT NOT NULL, - "operatorId" TEXT NOT NULL DEFAULT 'op_edr', - - CONSTRAINT "TrainService_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Trip" ( - "id" TEXT NOT NULL, - "serviceId" TEXT NOT NULL, - "originStationId" TEXT NOT NULL, - "destinationStationId" TEXT NOT NULL, - "departureAt" TIMESTAMP(3) NOT NULL, - "arrivalAt" TIMESTAMP(3) NOT NULL, - "durationMinutes" INTEGER NOT NULL, - "status" "TripStatus" NOT NULL DEFAULT 'SCHEDULED', - "stopsCount" INTEGER NOT NULL DEFAULT 0, - "onTimePercent" INTEGER NOT NULL DEFAULT 100, - "carbonRating" TEXT NOT NULL DEFAULT 'A', - - CONSTRAINT "Trip_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "TripStopTime" ( - "id" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "stationId" TEXT NOT NULL, - "sequence" INTEGER NOT NULL, - "plannedArrivalAt" TIMESTAMP(3), - "plannedDepartureAt" TIMESTAMP(3), - "actualArrivalAt" TIMESTAMP(3), - "status" "StopStatus" NOT NULL DEFAULT 'UPCOMING', - - CONSTRAINT "TripStopTime_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "TripLiveStatus" ( - "id" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "state" TEXT NOT NULL, - "currentLocationLabel" TEXT, - "progressPercent" INTEGER NOT NULL DEFAULT 0, - "delayMinutes" INTEGER NOT NULL DEFAULT 0, - "currentSpeedKph" INTEGER, - "platformLabel" TEXT, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "TripLiveStatus_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Coach" ( - "id" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "label" TEXT NOT NULL, - "serviceClass" "ServiceClass" NOT NULL, - - CONSTRAINT "Coach_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Seat" ( - "id" TEXT NOT NULL, - "coachId" TEXT NOT NULL, - "row" INTEGER NOT NULL, - "col" TEXT NOT NULL, - "label" TEXT NOT NULL, - "kind" "SeatKind" NOT NULL DEFAULT 'STANDARD', - "status" "SeatStatus" NOT NULL DEFAULT 'AVAILABLE', - "heldUntil" TIMESTAMP(3), - - CONSTRAINT "Seat_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "SeatHold" ( - "id" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "seatIds" TEXT[], - "fareQuoteId" TEXT, - "passengerId" TEXT NOT NULL, - "expiresAt" TIMESTAMP(3) NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "SeatHold_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FareRule" ( - "id" TEXT NOT NULL, - "tripId" TEXT, - "route" TEXT, - "serviceClass" "ServiceClass" NOT NULL, - "baseFareMinor" INTEGER NOT NULL, - "currency" TEXT NOT NULL DEFAULT 'ETB', - "refundable" BOOLEAN NOT NULL DEFAULT true, - "validFrom" TIMESTAMP(3) NOT NULL, - "validUntil" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "FareRule_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Booking" ( - "id" TEXT NOT NULL, - "bookingRef" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "status" "BookingStatus" NOT NULL DEFAULT 'DRAFT', - "currency" TEXT NOT NULL DEFAULT 'ETB', - "totalMinor" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Booking_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "BookingSeat" ( - "id" TEXT NOT NULL, - "bookingId" TEXT NOT NULL, - "seatId" TEXT NOT NULL, - "passengerName" TEXT NOT NULL, - "idDocumentType" TEXT, - "idDocumentNumber" TEXT, - - CONSTRAINT "BookingSeat_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "PaymentMethod" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "type" "PaymentMethodType" NOT NULL, - "displayName" TEXT NOT NULL, - "maskedHint" TEXT, - "isDefault" BOOLEAN NOT NULL DEFAULT false, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "PaymentMethod_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "PaymentIntent" ( - "id" TEXT NOT NULL, - "bookingId" TEXT NOT NULL, - "amountMinor" INTEGER NOT NULL, - "currency" TEXT NOT NULL DEFAULT 'ETB', - "method" "PaymentMethodType" NOT NULL, - "status" "PaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_ACTION', - "providerRef" TEXT, - "clientAction" JSONB, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "PaymentIntent_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Ticket" ( - "id" TEXT NOT NULL, - "bookingId" TEXT NOT NULL, - "bookingRef" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'CONFIRMED', - "qrPayload" TEXT NOT NULL, - "issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "validatedAt" TIMESTAMP(3), - "validatorId" TEXT, - - CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LoyaltyAccount" ( - "id" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "pointsBalance" INTEGER NOT NULL DEFAULT 0, - "tier" "LoyaltyTier" NOT NULL DEFAULT 'BRONZE', - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "LoyaltyAccount_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LoyaltyLedgerEntry" ( - "id" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "delta" INTEGER NOT NULL, - "reason" "LoyaltyLedgerReason" NOT NULL, - "bookingId" TEXT, - "balanceAfter" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "LoyaltyLedgerEntry_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LoyaltyReward" ( - "id" TEXT NOT NULL, - "accountId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "costPoints" INTEGER NOT NULL, - "available" BOOLEAN NOT NULL DEFAULT true, - "description" TEXT, - - CONSTRAINT "LoyaltyReward_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "WalletAccount" ( - "id" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "balanceMinor" INTEGER NOT NULL DEFAULT 0, - "currency" TEXT NOT NULL DEFAULT 'ETB', - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "WalletAccount_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "WalletLedgerEntry" ( - "id" TEXT NOT NULL, - "walletId" TEXT NOT NULL, - "type" "WalletLedgerType" NOT NULL, - "amountMinor" INTEGER NOT NULL, - "balanceAfterMinor" INTEGER NOT NULL, - "description" TEXT NOT NULL, - "relatedBookingId" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "WalletLedgerEntry_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Notification" ( - "id" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "body" TEXT NOT NULL, - "category" "NotificationCategory" NOT NULL, - "read" BOOLEAN NOT NULL DEFAULT false, - "deepLink" TEXT, - "metadata" JSONB, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Promotion" ( - "id" TEXT NOT NULL, - "title" TEXT NOT NULL, - "subtitle" TEXT, - "code" TEXT NOT NULL, - "percentOff" INTEGER, - "amountOffMinor" INTEGER, - "validUntil" TIMESTAMP(3) NOT NULL, - "ctaLabel" TEXT, - "deepLink" TEXT, - "active" BOOLEAN NOT NULL DEFAULT true, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Promotion_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "StationCrowdSignal" ( - "id" TEXT NOT NULL, - "stationId" TEXT NOT NULL, - "level" TEXT NOT NULL, - "label" TEXT NOT NULL, - "statusLabel" TEXT NOT NULL, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "StationCrowdSignal_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "WeatherAlert" ( - "id" TEXT NOT NULL, - "region" TEXT NOT NULL, - "severity" TEXT NOT NULL, - "title" TEXT NOT NULL, - "message" TEXT NOT NULL, - "validUntil" TIMESTAMP(3) NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "WeatherAlert_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "MenuCategory" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - - CONSTRAINT "MenuCategory_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "MenuItem" ( - "id" TEXT NOT NULL, - "tripId" TEXT NOT NULL, - "categoryId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "priceMinor" INTEGER NOT NULL, - "currency" TEXT NOT NULL DEFAULT 'ETB', - "available" BOOLEAN NOT NULL DEFAULT true, - - CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FoodOrder" ( - "id" TEXT NOT NULL, - "bookingId" TEXT NOT NULL, - "status" "FoodOrderStatus" NOT NULL DEFAULT 'PENDING', - "totalMinor" INTEGER NOT NULL, - "currency" TEXT NOT NULL DEFAULT 'ETB', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "FoodOrder_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FoodOrderItem" ( - "id" TEXT NOT NULL, - "orderId" TEXT NOT NULL, - "menuItemId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "quantity" INTEGER NOT NULL, - "lineTotalMinor" INTEGER NOT NULL, - - CONSTRAINT "FoodOrderItem_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FaqCategory" ( - "id" TEXT NOT NULL, - "title" TEXT NOT NULL, - "iconKey" TEXT, - - CONSTRAINT "FaqCategory_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FaqArticle" ( - "id" TEXT NOT NULL, - "categoryId" TEXT NOT NULL, - "question" TEXT NOT NULL, - "answerMarkdown" TEXT NOT NULL, - "rank" INTEGER NOT NULL DEFAULT 0, - - CONSTRAINT "FaqArticle_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "SupportConversation" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "status" "SupportConversationStatus" NOT NULL DEFAULT 'OPEN', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "SupportConversation_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "SupportMessage" ( - "id" TEXT NOT NULL, - "conversationId" TEXT NOT NULL, - "sender" "SupportSender" NOT NULL, - "text" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "SupportMessage_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "UserPreferences" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "pushEnabled" BOOLEAN NOT NULL DEFAULT true, - "emailEnabled" BOOLEAN NOT NULL DEFAULT true, - "smsEnabled" BOOLEAN NOT NULL DEFAULT false, - "promosEnabled" BOOLEAN NOT NULL DEFAULT true, - "biometricEnabled" BOOLEAN NOT NULL DEFAULT false, - "twoFactorEnabled" BOOLEAN NOT NULL DEFAULT false, - "defaultPaymentMethodId" TEXT, - "autoDownloadTickets" BOOLEAN NOT NULL DEFAULT true, - "dataSharing" BOOLEAN NOT NULL DEFAULT false, - "locale" TEXT NOT NULL DEFAULT 'en', - "darkMode" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "UserPreferences_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Device" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "platform" "DevicePlatform" NOT NULL, - "name" TEXT NOT NULL, - "pushToken" TEXT, - "trusted" BOOLEAN NOT NULL DEFAULT false, - "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Device_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "SavedRoute" ( - "id" TEXT NOT NULL, - "passengerId" TEXT NOT NULL, - "fromStationId" TEXT NOT NULL, - "toStationId" TEXT NOT NULL, - "fromName" TEXT NOT NULL, - "toName" TEXT NOT NULL, - "tripCount" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "SavedRoute_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone"); - --- CreateIndex -CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "Passenger_userId_key" ON "Passenger"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "Station_code_key" ON "Station"("code"); - --- CreateIndex -CREATE UNIQUE INDEX "TrainService_number_key" ON "TrainService"("number"); - --- CreateIndex -CREATE UNIQUE INDEX "TripStopTime_tripId_sequence_key" ON "TripStopTime"("tripId", "sequence"); - --- CreateIndex -CREATE UNIQUE INDEX "TripLiveStatus_tripId_key" ON "TripLiveStatus"("tripId"); - --- CreateIndex -CREATE UNIQUE INDEX "Coach_tripId_label_key" ON "Coach"("tripId", "label"); - --- CreateIndex -CREATE UNIQUE INDEX "Seat_coachId_row_col_key" ON "Seat"("coachId", "row", "col"); - --- CreateIndex -CREATE UNIQUE INDEX "Booking_bookingRef_key" ON "Booking"("bookingRef"); - --- CreateIndex -CREATE UNIQUE INDEX "PaymentIntent_bookingId_key" ON "PaymentIntent"("bookingId"); - --- CreateIndex -CREATE UNIQUE INDEX "Ticket_bookingId_key" ON "Ticket"("bookingId"); - --- CreateIndex -CREATE UNIQUE INDEX "LoyaltyAccount_passengerId_key" ON "LoyaltyAccount"("passengerId"); - --- CreateIndex -CREATE UNIQUE INDEX "WalletAccount_passengerId_key" ON "WalletAccount"("passengerId"); - --- CreateIndex -CREATE UNIQUE INDEX "Promotion_code_key" ON "Promotion"("code"); - --- CreateIndex -CREATE UNIQUE INDEX "UserPreferences_userId_key" ON "UserPreferences"("userId"); - --- AddForeignKey -ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Passenger" ADD CONSTRAINT "Passenger_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "TravelerProfile" ADD CONSTRAINT "TravelerProfile_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Trip" ADD CONSTRAINT "Trip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "TrainService"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Trip" ADD CONSTRAINT "Trip_originStationId_fkey" FOREIGN KEY ("originStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Trip" ADD CONSTRAINT "Trip_destinationStationId_fkey" FOREIGN KEY ("destinationStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "TripLiveStatus" ADD CONSTRAINT "TripLiveStatus_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Coach" ADD CONSTRAINT "Coach_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Seat" ADD CONSTRAINT "Seat_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "Coach"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Booking" ADD CONSTRAINT "Booking_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Booking" ADD CONSTRAINT "Booking_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_seatId_fkey" FOREIGN KEY ("seatId") REFERENCES "Seat"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "PaymentIntent" ADD CONSTRAINT "PaymentIntent_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LoyaltyAccount" ADD CONSTRAINT "LoyaltyAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LoyaltyLedgerEntry" ADD CONSTRAINT "LoyaltyLedgerEntry_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LoyaltyReward" ADD CONSTRAINT "LoyaltyReward_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "WalletAccount" ADD CONSTRAINT "WalletAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "WalletLedgerEntry" ADD CONSTRAINT "WalletLedgerEntry_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "WalletAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Notification" ADD CONSTRAINT "Notification_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "StationCrowdSignal" ADD CONSTRAINT "StationCrowdSignal_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "MenuCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "FoodOrder" ADD CONSTRAINT "FoodOrder_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "FoodOrderItem" ADD CONSTRAINT "FoodOrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "FoodOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "FaqArticle" ADD CONSTRAINT "FaqArticle_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "FaqCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "SupportMessage" ADD CONSTRAINT "SupportMessage_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "SupportConversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "UserPreferences" ADD CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Device" ADD CONSTRAINT "Device_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "SavedRoute" ADD CONSTRAINT "SavedRoute_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/edr-passenger-api/prisma/migrations/20260516000000_seat_class_model/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260516000000_seat_class_model/migration.sql new file mode 100644 index 000000000..5ff34f87e --- /dev/null +++ b/apps/edr-passenger-api/prisma/migrations/20260516000000_seat_class_model/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable SeatClass (runs before initial migration) +CREATE TABLE IF NOT EXISTS "SeatClass" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "basePrice" INTEGER NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(), + + CONSTRAINT "SeatClass_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX IF NOT EXISTS "SeatClass_name_key" ON "SeatClass"("name"); diff --git a/apps/edr-passenger-api/prisma/migrations/20260516000001_seat_class_updated_at_default/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260516000001_seat_class_updated_at_default/migration.sql new file mode 100644 index 000000000..8f938d14e --- /dev/null +++ b/apps/edr-passenger-api/prisma/migrations/20260516000001_seat_class_updated_at_default/migration.sql @@ -0,0 +1,2 @@ +-- updatedAt default already set in initial migration, no-op +SELECT 1; diff --git a/apps/edr-passenger-api/prisma/migrations/20260522115951_initial/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260522115951_initial/migration.sql new file mode 100644 index 000000000..cb6c033f5 --- /dev/null +++ b/apps/edr-passenger-api/prisma/migrations/20260522115951_initial/migration.sql @@ -0,0 +1,1352 @@ +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('PASSENGER', 'AGENT', 'SUPERVISOR', 'ADMIN', 'STAFF'); + +-- CreateEnum +CREATE TYPE "TripStatus" AS ENUM ('SCHEDULED', 'BOARDING', 'EN_ROUTE', 'ARRIVED', 'CANCELLED', 'DELAYED'); + +-- CreateEnum +CREATE TYPE "SeatKind" AS ENUM ('STANDARD', 'PREMIUM', 'ACCESSIBLE'); + +-- CreateEnum +CREATE TYPE "SeatStatus" AS ENUM ('AVAILABLE', 'HELD', 'BOOKED', 'BLOCKED'); + +-- CreateEnum +CREATE TYPE "PassengerCategory" AS ENUM ('ADULT', 'CHILD'); + +-- CreateEnum +CREATE TYPE "IdDocumentType" AS ENUM ('NATIONAL_ID', 'PASSPORT', 'DRIVING_LICENSE', 'OTHER'); + +-- CreateEnum +CREATE TYPE "Currency" AS ENUM ('ETB', 'DJF', 'USD'); + +-- CreateEnum +CREATE TYPE "BookingStatus" AS ENUM ('DRAFT', 'PENDING_PAYMENT', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW', 'REFUNDED'); + +-- CreateEnum +CREATE TYPE "PaymentMethodType" AS ENUM ('TELEBIRR', 'CBE_BIRR', 'EBIRR', 'CARD', 'WALLET'); + +-- CreateEnum +CREATE TYPE "PaymentIntentStatus" AS ENUM ('REQUIRES_ACTION', 'PROCESSING', 'SUCCEEDED', 'FAILED', 'CANCELLED', 'REFUNDED'); + +-- CreateEnum +CREATE TYPE "WalletLedgerType" AS ENUM ('CREDIT', 'DEBIT'); + +-- CreateEnum +CREATE TYPE "NotificationCategory" AS ENUM ('BOOKING', 'PAYMENT', 'DISRUPTION', 'PROMOTION', 'SYSTEM'); + +-- CreateEnum +CREATE TYPE "StopStatus" AS ENUM ('COMPLETED', 'APPROACHING', 'CURRENT', 'UPCOMING'); + +-- CreateEnum +CREATE TYPE "SupportConversationStatus" AS ENUM ('OPEN', 'RESOLVED', 'CLOSED'); + +-- CreateEnum +CREATE TYPE "SupportSender" AS ENUM ('USER', 'BOT', 'AGENT'); + +-- CreateEnum +CREATE TYPE "LoyaltyTier" AS ENUM ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM'); + +-- CreateEnum +CREATE TYPE "LoyaltyLedgerReason" AS ENUM ('TRIP_COMPLETED', 'REWARD_REDEEMED', 'PROMO_BONUS', 'MANUAL_ADJUSTMENT', 'EXPIRY'); + +-- CreateEnum +CREATE TYPE "FoodOrderStatus" AS ENUM ('PENDING', 'PREPARING', 'READY', 'DELIVERED', 'CANCELLED'); + +-- CreateEnum +CREATE TYPE "DevicePlatform" AS ENUM ('IOS', 'ANDROID', 'WEB'); + +-- AlterTable +ALTER TABLE "SeatClass" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "fullName" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "role" "UserRole" NOT NULL DEFAULT 'PASSENGER', + "nationality" TEXT, + "nationalityCode" TEXT, + "passportNumber" TEXT, + "nationalId" TEXT, + "failedLoginAttempts" INTEGER NOT NULL DEFAULT 0, + "lockedUntil" TIMESTAMP(3), + "blockedUntil" TIMESTAMP(3), + "lastLoginAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "lastActivityAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Passenger" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "defaultTravelerProfileId" TEXT, + "preferredLanguage" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Passenger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TravelerProfile" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "fullName" TEXT NOT NULL, + "relationship" TEXT NOT NULL, + "dateOfBirth" TIMESTAMP(3), + "nationalId" TEXT, + "notes" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "TravelerProfile_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Station" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "city" TEXT NOT NULL, + "countryCode" TEXT, + "isOperational" BOOLEAN NOT NULL DEFAULT true, + "timezone" TEXT NOT NULL DEFAULT 'Africa/Addis_Ababa', + "lat" DECIMAL(9,6) NOT NULL, + "lng" DECIMAL(9,6) NOT NULL, + + CONSTRAINT "Station_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Train" ( + "id" TEXT NOT NULL, + "number" TEXT NOT NULL, + "name" TEXT NOT NULL, + "operatorId" TEXT NOT NULL DEFAULT 'op_edr', + "operatorName" TEXT, + "description" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Train_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TrainSchedule" ( + "id" TEXT NOT NULL, + "trainId" TEXT NOT NULL, + "routeId" TEXT, + "originStationId" TEXT NOT NULL, + "destinationStationId" TEXT NOT NULL, + "departureAt" TIMESTAMP(3) NOT NULL, + "arrivalAt" TIMESTAMP(3) NOT NULL, + "durationMinutes" INTEGER NOT NULL, + "status" "TripStatus" NOT NULL DEFAULT 'SCHEDULED', + "stopsCount" INTEGER NOT NULL DEFAULT 0, + "reservedCount" INTEGER NOT NULL DEFAULT 0, + "onTimePercent" INTEGER NOT NULL DEFAULT 100, + "carbonRating" TEXT NOT NULL DEFAULT 'A', + "notes" TEXT, + + CONSTRAINT "TrainSchedule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TripStopTime" ( + "id" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "stationId" TEXT NOT NULL, + "sequence" INTEGER NOT NULL, + "plannedArrivalAt" TIMESTAMP(3), + "plannedDepartureAt" TIMESTAMP(3), + "actualArrivalAt" TIMESTAMP(3), + "status" "StopStatus" NOT NULL DEFAULT 'UPCOMING', + + CONSTRAINT "TripStopTime_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TripLiveStatus" ( + "id" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "state" TEXT NOT NULL, + "currentLocationLabel" TEXT, + "progressPercent" INTEGER NOT NULL DEFAULT 0, + "delayMinutes" INTEGER NOT NULL DEFAULT 0, + "currentSpeedKph" INTEGER, + "platformLabel" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "TripLiveStatus_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Coach" ( + "id" TEXT NOT NULL, + "coachNumber" TEXT NOT NULL, + "label" TEXT NOT NULL, + "seatClassId" TEXT NOT NULL, + "coachType" TEXT, + "mode" TEXT NOT NULL DEFAULT 'seat', + "seatArrangement" TEXT, + "bedArrangement" TEXT, + "amenities" JSONB, + "totalUnits" INTEGER NOT NULL DEFAULT 0, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Coach_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CoachAssignment" ( + "id" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "coachId" TEXT NOT NULL, + "positionNumber" INTEGER NOT NULL, + "isOperational" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "CoachAssignment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Seat" ( + "id" TEXT NOT NULL, + "coachId" TEXT NOT NULL, + "row" INTEGER NOT NULL, + "col" TEXT NOT NULL, + "label" TEXT NOT NULL, + "seatNumber" TEXT, + "kind" "SeatKind" NOT NULL DEFAULT 'STANDARD', + "status" "SeatStatus" NOT NULL DEFAULT 'AVAILABLE', + "heldUntil" TIMESTAMP(3), + "isWindow" BOOLEAN NOT NULL DEFAULT false, + "isAisle" BOOLEAN NOT NULL DEFAULT false, + "bedPosition" TEXT, + "premiumFeeMinor" INTEGER NOT NULL DEFAULT 0, + "eligibility" TEXT, + + CONSTRAINT "Seat_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SeatHold" ( + "id" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "seatIds" TEXT[], + "fareQuoteId" TEXT, + "passengerId" TEXT NOT NULL, + "createdBy" TEXT, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "SeatHold_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FareRule" ( + "id" TEXT NOT NULL, + "tripId" TEXT, + "route" TEXT, + "seatClassId" TEXT NOT NULL, + "baseFareMinor" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "refundable" BOOLEAN NOT NULL DEFAULT true, + "validFrom" TIMESTAMP(3) NOT NULL, + "validUntil" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "FareRule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Booking" ( + "id" TEXT NOT NULL, + "bookingRef" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "status" "BookingStatus" NOT NULL DEFAULT 'DRAFT', + "currency" TEXT NOT NULL DEFAULT 'ETB', + "totalMinor" INTEGER NOT NULL, + "adultCount" INTEGER NOT NULL DEFAULT 1, + "childCount" INTEGER NOT NULL DEFAULT 0, + "displayCurrency" "Currency", + "displayTotalMinor" INTEGER, + "bookingType" TEXT NOT NULL DEFAULT 'ONE_WAY', + "userAgent" TEXT, + "source" TEXT NOT NULL DEFAULT 'WEB', + "promoCode" TEXT, + "paidAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Booking_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BookingSeat" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "seatId" TEXT NOT NULL, + "passengerName" TEXT NOT NULL, + "dateOfBirth" TIMESTAMP(3), + "passengerCategory" "PassengerCategory" NOT NULL DEFAULT 'ADULT', + "idDocumentType" "IdDocumentType", + "idDocumentNumber" TEXT, + "passportNumber" TEXT, + "passportCountry" TEXT, + "verifaydaVerified" BOOLEAN NOT NULL DEFAULT false, + "verifaydaData" JSONB, + "seatLabelSnapshot" TEXT, + "fareMinor" INTEGER, + "displayCurrency" "Currency", + "displayFareMinor" INTEGER, + + CONSTRAINT "BookingSeat_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PaymentMethod" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" "PaymentMethodType" NOT NULL, + "displayName" TEXT NOT NULL, + "maskedHint" TEXT, + "providerId" TEXT, + "isDefault" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PaymentMethod_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PaymentIntent" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "amountMinor" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "method" "PaymentMethodType" NOT NULL, + "provider" TEXT, + "status" "PaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_ACTION', + "providerRef" TEXT, + "clientAction" JSONB, + "merchantOrderId" TEXT, + "providerOrderId" TEXT, + "providerTxnId" TEXT, + "rawInitiation" JSONB, + "paidAt" TIMESTAMP(3), + "refundedAt" TIMESTAMP(3), + "captureMethod" TEXT, + "failureCode" TEXT, + "failureMessage" TEXT, + "expiresAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PaymentIntent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PaymentWebhookEvent" ( + "id" TEXT NOT NULL, + "provider" "PaymentMethodType" NOT NULL, + "externalEventId" TEXT NOT NULL, + "merchantOrderId" TEXT, + "providerTxnId" TEXT, + "signatureValid" BOOLEAN NOT NULL, + "status" TEXT NOT NULL, + "payload" JSONB NOT NULL, + "receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "processedAt" TIMESTAMP(3), + "processingError" TEXT, + + CONSTRAINT "PaymentWebhookEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PaymentRefund" ( + "id" TEXT NOT NULL, + "paymentIntentId" TEXT NOT NULL, + "amountMinor" INTEGER NOT NULL, + "reason" TEXT, + "providerRefundId" TEXT, + "status" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PaymentRefund_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Ticket" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "bookingRef" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'CONFIRMED', + "qrPayload" TEXT NOT NULL, + "barcodePayload" TEXT, + "pdfUrl" TEXT, + "deliveryChannel" TEXT NOT NULL DEFAULT 'EMAIL', + "issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "validatedAt" TIMESTAMP(3), + "validatorId" TEXT, + + CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoyaltyAccount" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "pointsBalance" INTEGER NOT NULL DEFAULT 0, + "lifetimePoints" INTEGER NOT NULL DEFAULT 0, + "tier" "LoyaltyTier" NOT NULL DEFAULT 'BRONZE', + "tierUpdatedAt" TIMESTAMP(3), + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LoyaltyAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoyaltyLedgerEntry" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "delta" INTEGER NOT NULL, + "reason" "LoyaltyLedgerReason" NOT NULL, + "bookingId" TEXT, + "balanceAfter" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "LoyaltyLedgerEntry_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoyaltyReward" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "title" TEXT NOT NULL, + "costPoints" INTEGER NOT NULL, + "available" BOOLEAN NOT NULL DEFAULT true, + "description" TEXT, + + CONSTRAINT "LoyaltyReward_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WalletAccount" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "balanceMinor" INTEGER NOT NULL DEFAULT 0, + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "holdMinor" INTEGER NOT NULL DEFAULT 0, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WalletAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WalletLedgerEntry" ( + "id" TEXT NOT NULL, + "walletId" TEXT NOT NULL, + "type" "WalletLedgerType" NOT NULL, + "amountMinor" INTEGER NOT NULL, + "balanceAfterMinor" INTEGER NOT NULL, + "description" TEXT NOT NULL, + "relatedBookingId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "WalletLedgerEntry_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Notification" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "category" "NotificationCategory" NOT NULL, + "read" BOOLEAN NOT NULL DEFAULT false, + "deepLink" TEXT, + "metadata" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Promotion" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "subtitle" TEXT, + "code" TEXT NOT NULL, + "percentOff" INTEGER, + "amountOffMinor" INTEGER, + "validUntil" TIMESTAMP(3) NOT NULL, + "ctaLabel" TEXT, + "deepLink" TEXT, + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Promotion_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "StationCrowdSignal" ( + "id" TEXT NOT NULL, + "stationId" TEXT NOT NULL, + "level" TEXT NOT NULL, + "label" TEXT NOT NULL, + "statusLabel" TEXT NOT NULL, + "confidence" INTEGER, + "observedAt" TIMESTAMP(3), + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "StationCrowdSignal_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WeatherAlert" ( + "id" TEXT NOT NULL, + "region" TEXT NOT NULL, + "severity" TEXT NOT NULL, + "title" TEXT NOT NULL, + "message" TEXT NOT NULL, + "validUntil" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "WeatherAlert_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MenuCategory" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + + CONSTRAINT "MenuCategory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MenuItem" ( + "id" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "categoryId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "priceMinor" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "available" BOOLEAN NOT NULL DEFAULT true, + "availableUntil" TIMESTAMP(3), + + CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FoodOrder" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "status" "FoodOrderStatus" NOT NULL DEFAULT 'PENDING', + "totalMinor" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "specialInstructions" TEXT, + "estimatedReadyAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "FoodOrder_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FoodOrderItem" ( + "id" TEXT NOT NULL, + "orderId" TEXT NOT NULL, + "menuItemId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "quantity" INTEGER NOT NULL, + "unitPriceMinor" INTEGER, + "lineTotalMinor" INTEGER NOT NULL, + + CONSTRAINT "FoodOrderItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FaqCategory" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "iconKey" TEXT, + + CONSTRAINT "FaqCategory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FaqArticle" ( + "id" TEXT NOT NULL, + "categoryId" TEXT NOT NULL, + "question" TEXT NOT NULL, + "answerMarkdown" TEXT NOT NULL, + "rank" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "FaqArticle_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SupportConversation" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "assignedAgentId" TEXT, + "status" "SupportConversationStatus" NOT NULL DEFAULT 'OPEN', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "SupportConversation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SupportMessage" ( + "id" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "sender" "SupportSender" NOT NULL, + "text" TEXT NOT NULL, + "attachments" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "SupportMessage_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UserPreferences" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pushEnabled" BOOLEAN NOT NULL DEFAULT true, + "emailEnabled" BOOLEAN NOT NULL DEFAULT true, + "smsEnabled" BOOLEAN NOT NULL DEFAULT false, + "promosEnabled" BOOLEAN NOT NULL DEFAULT true, + "biometricEnabled" BOOLEAN NOT NULL DEFAULT false, + "twoFactorEnabled" BOOLEAN NOT NULL DEFAULT false, + "defaultPaymentMethodId" TEXT, + "autoDownloadTickets" BOOLEAN NOT NULL DEFAULT true, + "dataSharing" BOOLEAN NOT NULL DEFAULT false, + "locale" TEXT NOT NULL DEFAULT 'en', + "darkMode" BOOLEAN NOT NULL DEFAULT false, + "language" TEXT NOT NULL DEFAULT 'en', + + CONSTRAINT "UserPreferences_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Device" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "platform" "DevicePlatform" NOT NULL, + "name" TEXT NOT NULL, + "pushToken" TEXT, + "trusted" BOOLEAN NOT NULL DEFAULT false, + "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Device_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SavedRoute" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "fromStationId" TEXT NOT NULL, + "toStationId" TEXT NOT NULL, + "fromName" TEXT NOT NULL, + "toName" TEXT NOT NULL, + "tripCount" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "SavedRoute_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Journey" ( + "id" TEXT NOT NULL, + "passengerId" TEXT NOT NULL, + "status" TEXT NOT NULL, + "totalMinor" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Journey_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "JourneySegment" ( + "id" TEXT NOT NULL, + "journeyId" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "segmentOrder" INTEGER NOT NULL, + "seatId" TEXT, + "coachId" TEXT, + "departureStationId" TEXT NOT NULL, + "arrivalStationId" TEXT NOT NULL, + + CONSTRAINT "JourneySegment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OtpCode" ( + "id" TEXT NOT NULL, + "userId" TEXT, + "email" TEXT, + "phone" TEXT, + "code" TEXT NOT NULL, + "purpose" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "verified" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OtpCode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PasswordResetToken" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "used" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Route" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "effectiveFrom" TIMESTAMP(3) NOT NULL, + "effectiveUntil" TIMESTAMP(3), + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Route_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RouteStop" ( + "id" TEXT NOT NULL, + "routeId" TEXT NOT NULL, + "stationId" TEXT NOT NULL, + "sequence" INTEGER NOT NULL, + "distanceKm" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RouteStop_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RouteFareRule" ( + "id" TEXT NOT NULL, + "routeId" TEXT NOT NULL, + "seatClassId" TEXT NOT NULL, + "passengerCategory" "PassengerCategory" NOT NULL DEFAULT 'ADULT', + "baseFareMinor" INTEGER NOT NULL, + "discountPercent" INTEGER, + "taxPercent" INTEGER, + "surchargeMinor" INTEGER, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "validFrom" TIMESTAMP(3) NOT NULL, + "validUntil" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RouteFareRule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Agent" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "agentCode" TEXT NOT NULL, + "stationId" TEXT, + "commissionRate" INTEGER NOT NULL DEFAULT 5, + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Agent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentBooking" ( + "id" TEXT NOT NULL, + "agentId" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "paymentMethod" TEXT NOT NULL, + "cashReceived" INTEGER, + "changeGiven" INTEGER, + "paperTicket" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AgentBooking_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentShift" ( + "id" TEXT NOT NULL, + "agentId" TEXT NOT NULL, + "openedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "closedAt" TIMESTAMP(3), + "openingBalance" INTEGER NOT NULL DEFAULT 0, + "closingBalance" INTEGER, + "reconciled" BOOLEAN NOT NULL DEFAULT false, + "notes" TEXT, + + CONSTRAINT "AgentShift_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentCommission" ( + "id" TEXT NOT NULL, + "agentId" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "amountMinor" INTEGER NOT NULL, + "rate" INTEGER NOT NULL, + "paidAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AgentCommission_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BookingModification" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "modifiedBy" TEXT NOT NULL, + "modificationType" TEXT NOT NULL, + "oldData" JSONB NOT NULL, + "newData" JSONB NOT NULL, + "fareAdjustment" INTEGER NOT NULL DEFAULT 0, + "reason" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "BookingModification_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BookingCancellation" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "cancelledBy" TEXT NOT NULL, + "reason" TEXT, + "refundAmount" INTEGER NOT NULL, + "refundMethod" TEXT NOT NULL, + "refundStatus" TEXT NOT NULL, + "processedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "BookingCancellation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GateValidationLog" ( + "id" TEXT NOT NULL, + "ticketId" TEXT NOT NULL, + "validatorId" TEXT NOT NULL, + "gateId" TEXT, + "status" TEXT NOT NULL, + "reason" TEXT, + "validatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "GateValidationLog_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BaggageAllowance" ( + "id" TEXT NOT NULL, + "seatClassId" TEXT NOT NULL, + "maxWeightKg" INTEGER NOT NULL, + "maxPiecesCount" INTEGER NOT NULL, + "excessFeePerKg" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'ETB', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "BaggageAllowance_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BaggageBooking" ( + "id" TEXT NOT NULL, + "bookingId" TEXT NOT NULL, + "weightKg" INTEGER NOT NULL, + "piecesCount" INTEGER NOT NULL, + "excessFeeMinor" INTEGER NOT NULL DEFAULT 0, + "paid" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "BaggageBooking_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AuditLog" ( + "id" TEXT NOT NULL, + "userId" TEXT, + "action" TEXT NOT NULL, + "entityType" TEXT NOT NULL, + "entityId" TEXT, + "oldData" JSONB, + "newData" JSONB, + "ipAddress" TEXT, + "userAgent" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "NotificationTemplate" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "channel" TEXT NOT NULL, + "subject" TEXT, + "bodyTemplate" TEXT NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "NotificationTemplate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SeatBlock" ( + "id" TEXT NOT NULL, + "seatId" TEXT NOT NULL, + "reason" TEXT NOT NULL, + "blockedBy" TEXT NOT NULL, + "approvedBy" TEXT, + "blockedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "unblockAt" TIMESTAMP(3), + + CONSTRAINT "SeatBlock_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OperationalReport" ( + "id" TEXT NOT NULL, + "reportType" TEXT NOT NULL, + "dateFrom" TIMESTAMP(3) NOT NULL, + "dateTo" TIMESTAMP(3) NOT NULL, + "data" JSONB NOT NULL, + "generatedBy" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OperationalReport_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FraudRule" ( + "id" TEXT NOT NULL, + "type" TEXT NOT NULL, + "enabled" BOOLEAN NOT NULL DEFAULT true, + "threshold" DOUBLE PRECISION NOT NULL, + "config" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FraudRule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FraudAlert" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "eventType" TEXT NOT NULL, + "triggeredRules" TEXT[], + "context" JSONB NOT NULL, + "severity" TEXT NOT NULL DEFAULT 'MEDIUM', + "acknowledged" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "FraudAlert_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CurrencyExchangeRate" ( + "id" TEXT NOT NULL, + "fromCurrency" "Currency" NOT NULL, + "toCurrency" "Currency" NOT NULL, + "rate" DECIMAL(18,6) NOT NULL, + "effectiveDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "source" TEXT NOT NULL DEFAULT 'MANUAL', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "CurrencyExchangeRate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "VerifaydaVerification" ( + "id" TEXT NOT NULL, + "bookingId" TEXT, + "nationalId" TEXT NOT NULL, + "requestPayload" JSONB NOT NULL, + "responsePayload" JSONB, + "verified" BOOLEAN NOT NULL DEFAULT false, + "failureReason" TEXT, + "verifiedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "VerifaydaVerification_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "Passenger_userId_key" ON "Passenger"("userId"); + +-- CreateIndex +CREATE INDEX "Passenger_userId_idx" ON "Passenger"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Station_code_key" ON "Station"("code"); + +-- CreateIndex +CREATE INDEX "Station_city_countryCode_idx" ON "Station"("city", "countryCode"); + +-- CreateIndex +CREATE UNIQUE INDEX "Train_number_key" ON "Train"("number"); + +-- CreateIndex +CREATE INDEX "TrainSchedule_departureAt_originStationId_idx" ON "TrainSchedule"("departureAt", "originStationId"); + +-- CreateIndex +CREATE UNIQUE INDEX "TripStopTime_scheduleId_sequence_key" ON "TripStopTime"("scheduleId", "sequence"); + +-- CreateIndex +CREATE UNIQUE INDEX "TripLiveStatus_scheduleId_key" ON "TripLiveStatus"("scheduleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Coach_coachNumber_key" ON "Coach"("coachNumber"); + +-- CreateIndex +CREATE INDEX "CoachAssignment_scheduleId_idx" ON "CoachAssignment"("scheduleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "CoachAssignment_scheduleId_positionNumber_key" ON "CoachAssignment"("scheduleId", "positionNumber"); + +-- CreateIndex +CREATE UNIQUE INDEX "Seat_coachId_row_col_key" ON "Seat"("coachId", "row", "col"); + +-- CreateIndex +CREATE UNIQUE INDEX "Seat_coachId_seatNumber_key" ON "Seat"("coachId", "seatNumber"); + +-- CreateIndex +CREATE INDEX "SeatHold_expiresAt_idx" ON "SeatHold"("expiresAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "Booking_bookingRef_key" ON "Booking"("bookingRef"); + +-- CreateIndex +CREATE INDEX "Booking_passengerId_status_idx" ON "Booking"("passengerId", "status"); + +-- CreateIndex +CREATE INDEX "PaymentMethod_userId_isDefault_idx" ON "PaymentMethod"("userId", "isDefault"); + +-- CreateIndex +CREATE UNIQUE INDEX "PaymentIntent_bookingId_key" ON "PaymentIntent"("bookingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PaymentIntent_merchantOrderId_key" ON "PaymentIntent"("merchantOrderId"); + +-- CreateIndex +CREATE INDEX "PaymentIntent_providerOrderId_idx" ON "PaymentIntent"("providerOrderId"); + +-- CreateIndex +CREATE INDEX "PaymentIntent_providerTxnId_idx" ON "PaymentIntent"("providerTxnId"); + +-- CreateIndex +CREATE INDEX "PaymentWebhookEvent_merchantOrderId_idx" ON "PaymentWebhookEvent"("merchantOrderId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PaymentWebhookEvent_provider_externalEventId_key" ON "PaymentWebhookEvent"("provider", "externalEventId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Ticket_bookingId_key" ON "Ticket"("bookingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "LoyaltyAccount_passengerId_key" ON "LoyaltyAccount"("passengerId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WalletAccount_passengerId_key" ON "WalletAccount"("passengerId"); + +-- CreateIndex +CREATE INDEX "WalletAccount_passengerId_idx" ON "WalletAccount"("passengerId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Promotion_code_key" ON "Promotion"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "UserPreferences_userId_key" ON "UserPreferences"("userId"); + +-- CreateIndex +CREATE INDEX "OtpCode_email_phone_idx" ON "OtpCode"("email", "phone"); + +-- CreateIndex +CREATE UNIQUE INDEX "PasswordResetToken_token_key" ON "PasswordResetToken"("token"); + +-- CreateIndex +CREATE INDEX "PasswordResetToken_userId_idx" ON "PasswordResetToken"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Route_code_key" ON "Route"("code"); + +-- CreateIndex +CREATE INDEX "RouteStop_routeId_stationId_idx" ON "RouteStop"("routeId", "stationId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RouteStop_routeId_sequence_key" ON "RouteStop"("routeId", "sequence"); + +-- CreateIndex +CREATE INDEX "RouteFareRule_routeId_seatClassId_idx" ON "RouteFareRule"("routeId", "seatClassId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Agent_userId_key" ON "Agent"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Agent_agentCode_key" ON "Agent"("agentCode"); + +-- CreateIndex +CREATE UNIQUE INDEX "AgentBooking_bookingId_key" ON "AgentBooking"("bookingId"); + +-- CreateIndex +CREATE INDEX "AgentShift_agentId_openedAt_idx" ON "AgentShift"("agentId", "openedAt"); + +-- CreateIndex +CREATE INDEX "AgentCommission_agentId_paidAt_idx" ON "AgentCommission"("agentId", "paidAt"); + +-- CreateIndex +CREATE INDEX "BookingModification_bookingId_idx" ON "BookingModification"("bookingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "BookingCancellation_bookingId_key" ON "BookingCancellation"("bookingId"); + +-- CreateIndex +CREATE INDEX "GateValidationLog_ticketId_idx" ON "GateValidationLog"("ticketId"); + +-- CreateIndex +CREATE INDEX "GateValidationLog_validatorId_idx" ON "GateValidationLog"("validatorId"); + +-- CreateIndex +CREATE INDEX "BaggageBooking_bookingId_idx" ON "BaggageBooking"("bookingId"); + +-- CreateIndex +CREATE INDEX "AuditLog_userId_createdAt_idx" ON "AuditLog"("userId", "createdAt"); + +-- CreateIndex +CREATE INDEX "AuditLog_entityType_entityId_idx" ON "AuditLog"("entityType", "entityId"); + +-- CreateIndex +CREATE UNIQUE INDEX "NotificationTemplate_code_key" ON "NotificationTemplate"("code"); + +-- CreateIndex +CREATE INDEX "SeatBlock_seatId_idx" ON "SeatBlock"("seatId"); + +-- CreateIndex +CREATE INDEX "OperationalReport_reportType_dateFrom_idx" ON "OperationalReport"("reportType", "dateFrom"); + +-- CreateIndex +CREATE UNIQUE INDEX "FraudRule_type_key" ON "FraudRule"("type"); + +-- CreateIndex +CREATE INDEX "FraudAlert_userId_createdAt_idx" ON "FraudAlert"("userId", "createdAt"); + +-- CreateIndex +CREATE INDEX "FraudAlert_acknowledged_idx" ON "FraudAlert"("acknowledged"); + +-- CreateIndex +CREATE INDEX "CurrencyExchangeRate_fromCurrency_toCurrency_idx" ON "CurrencyExchangeRate"("fromCurrency", "toCurrency"); + +-- CreateIndex +CREATE UNIQUE INDEX "CurrencyExchangeRate_fromCurrency_toCurrency_effectiveDate_key" ON "CurrencyExchangeRate"("fromCurrency", "toCurrency", "effectiveDate"); + +-- CreateIndex +CREATE INDEX "VerifaydaVerification_nationalId_idx" ON "VerifaydaVerification"("nationalId"); + +-- CreateIndex +CREATE INDEX "VerifaydaVerification_bookingId_idx" ON "VerifaydaVerification"("bookingId"); + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Passenger" ADD CONSTRAINT "Passenger_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TravelerProfile" ADD CONSTRAINT "TravelerProfile_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TrainSchedule" ADD CONSTRAINT "TrainSchedule_trainId_fkey" FOREIGN KEY ("trainId") REFERENCES "Train"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TrainSchedule" ADD CONSTRAINT "TrainSchedule_originStationId_fkey" FOREIGN KEY ("originStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TrainSchedule" ADD CONSTRAINT "TrainSchedule_destinationStationId_fkey" FOREIGN KEY ("destinationStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TripLiveStatus" ADD CONSTRAINT "TripLiveStatus_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Coach" ADD CONSTRAINT "Coach_seatClassId_fkey" FOREIGN KEY ("seatClassId") REFERENCES "SeatClass"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CoachAssignment" ADD CONSTRAINT "CoachAssignment_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CoachAssignment" ADD CONSTRAINT "CoachAssignment_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "Coach"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Seat" ADD CONSTRAINT "Seat_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "Coach"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FareRule" ADD CONSTRAINT "FareRule_seatClassId_fkey" FOREIGN KEY ("seatClassId") REFERENCES "SeatClass"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Booking" ADD CONSTRAINT "Booking_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Booking" ADD CONSTRAINT "Booking_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_seatId_fkey" FOREIGN KEY ("seatId") REFERENCES "Seat"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PaymentIntent" ADD CONSTRAINT "PaymentIntent_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PaymentRefund" ADD CONSTRAINT "PaymentRefund_paymentIntentId_fkey" FOREIGN KEY ("paymentIntentId") REFERENCES "PaymentIntent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoyaltyAccount" ADD CONSTRAINT "LoyaltyAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoyaltyLedgerEntry" ADD CONSTRAINT "LoyaltyLedgerEntry_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoyaltyReward" ADD CONSTRAINT "LoyaltyReward_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WalletAccount" ADD CONSTRAINT "WalletAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WalletLedgerEntry" ADD CONSTRAINT "WalletLedgerEntry_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "WalletAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Notification" ADD CONSTRAINT "Notification_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StationCrowdSignal" ADD CONSTRAINT "StationCrowdSignal_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "MenuCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FoodOrder" ADD CONSTRAINT "FoodOrder_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FoodOrderItem" ADD CONSTRAINT "FoodOrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "FoodOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FaqArticle" ADD CONSTRAINT "FaqArticle_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "FaqCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SupportMessage" ADD CONSTRAINT "SupportMessage_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "SupportConversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserPreferences" ADD CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Device" ADD CONSTRAINT "Device_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SavedRoute" ADD CONSTRAINT "SavedRoute_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JourneySegment" ADD CONSTRAINT "JourneySegment_journeyId_fkey" FOREIGN KEY ("journeyId") REFERENCES "Journey"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JourneySegment" ADD CONSTRAINT "JourneySegment_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "TrainSchedule"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RouteStop" ADD CONSTRAINT "RouteStop_routeId_fkey" FOREIGN KEY ("routeId") REFERENCES "Route"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RouteFareRule" ADD CONSTRAINT "RouteFareRule_routeId_fkey" FOREIGN KEY ("routeId") REFERENCES "Route"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RouteFareRule" ADD CONSTRAINT "RouteFareRule_seatClassId_fkey" FOREIGN KEY ("seatClassId") REFERENCES "SeatClass"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Agent" ADD CONSTRAINT "Agent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentBooking" ADD CONSTRAINT "AgentBooking_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentBooking" ADD CONSTRAINT "AgentBooking_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentShift" ADD CONSTRAINT "AgentShift_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentCommission" ADD CONSTRAINT "AgentCommission_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BookingModification" ADD CONSTRAINT "BookingModification_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BookingCancellation" ADD CONSTRAINT "BookingCancellation_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GateValidationLog" ADD CONSTRAINT "GateValidationLog_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BaggageBooking" ADD CONSTRAINT "BaggageBooking_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SeatBlock" ADD CONSTRAINT "SeatBlock_seatId_fkey" FOREIGN KEY ("seatId") REFERENCES "Seat"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FraudAlert" ADD CONSTRAINT "FraudAlert_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/edr-passenger-api/prisma/migrations/migration_lock.toml b/apps/edr-passenger-api/prisma/migrations/migration_lock.toml index fbffa92c2..044d57cdb 100644 --- a/apps/edr-passenger-api/prisma/migrations/migration_lock.toml +++ b/apps/edr-passenger-api/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/apps/edr-passenger-api/prisma/reset-admin.d.ts b/apps/edr-passenger-api/prisma/reset-admin.d.ts deleted file mode 100644 index a4595fd95..000000000 --- a/apps/edr-passenger-api/prisma/reset-admin.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=reset-admin.d.ts.map \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/reset-admin.d.ts.map b/apps/edr-passenger-api/prisma/reset-admin.d.ts.map deleted file mode 100644 index d0f62a26c..000000000 --- a/apps/edr-passenger-api/prisma/reset-admin.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"reset-admin.d.ts","sourceRoot":"","sources":["reset-admin.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/reset-admin.js b/apps/edr-passenger-api/prisma/reset-admin.js deleted file mode 100644 index c00b804f6..000000000 --- a/apps/edr-passenger-api/prisma/reset-admin.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -const client_1 = require("@prisma/client"); -const bcrypt = __importStar(require("bcrypt")); -const prisma = new client_1.PrismaClient(); -async function main() { - const passwordHash = await bcrypt.hash('admin123', 10); - const user = await prisma.user.upsert({ - where: { email: 'admin@edr-platform.com' }, - update: { passwordHash, role: 'ADMIN' }, - create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash, role: 'ADMIN' }, - }); - console.log('โœ… Admin ready:', user.email); -} -main().catch(console.error).finally(() => prisma.$disconnect()); -//# sourceMappingURL=reset-admin.js.map \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/reset-admin.js.map b/apps/edr-passenger-api/prisma/reset-admin.js.map deleted file mode 100644 index a47b50176..000000000 --- a/apps/edr-passenger-api/prisma/reset-admin.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"reset-admin.js","sourceRoot":"","sources":["reset-admin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA8C;AAC9C,+CAAiC;AAEjC,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE;QAC1C,MAAM,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;QACvC,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;KACxH,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/reset-admin.ts b/apps/edr-passenger-api/prisma/reset-admin.ts deleted file mode 100644 index da9e5313f..000000000 --- a/apps/edr-passenger-api/prisma/reset-admin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PrismaClient } from '@prisma/client'; -import * as bcrypt from 'bcrypt'; - -const prisma = new PrismaClient(); - -async function main() { - const passwordHash = await bcrypt.hash('admin123', 10); - const user = await prisma.user.upsert({ - where: { email: 'admin@edr-platform.com' }, - update: { passwordHash, role: 'ADMIN' }, - create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash, role: 'ADMIN' }, - }); - console.log('โœ… Admin ready:', user.email); -} - -main().catch(console.error).finally(() => prisma.$disconnect()); diff --git a/apps/edr-passenger-api/prisma/schema.prisma b/apps/edr-passenger-api/prisma/schema.prisma index f4103c7a6..046faf2b1 100644 --- a/apps/edr-passenger-api/prisma/schema.prisma +++ b/apps/edr-passenger-api/prisma/schema.prisma @@ -3,14 +3,19 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") + schemas = ["passenger"] } enum UserRole { PASSENGER + AGENT + SUPERVISOR ADMIN STAFF + + @@schema("passenger") } enum TripStatus { @@ -20,12 +25,16 @@ enum TripStatus { ARRIVED CANCELLED DELAYED + + @@schema("passenger") } enum SeatKind { STANDARD PREMIUM ACCESSIBLE + + @@schema("passenger") } enum SeatStatus { @@ -33,12 +42,47 @@ enum SeatStatus { HELD BOOKED BLOCKED + + @@schema("passenger") } -enum ServiceClass { - ECONOMY - BUSINESS - FIRST +enum PassengerCategory { + ADULT + CHILD + + @@schema("passenger") +} + +enum IdDocumentType { + NATIONAL_ID + PASSPORT + DRIVING_LICENSE + OTHER + + @@schema("passenger") +} + +enum Currency { + ETB + DJF + USD + + @@schema("passenger") +} + +model SeatClass { + id String @id @default(uuid()) + name String @unique + description String? + basePrice Int + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + coaches Coach[] + fareRules FareRule[] + routeFareRules RouteFareRule[] + + @@schema("passenger") } enum BookingStatus { @@ -48,6 +92,9 @@ enum BookingStatus { CANCELLED COMPLETED NO_SHOW + REFUNDED + + @@schema("passenger") } enum PaymentMethodType { @@ -56,6 +103,8 @@ enum PaymentMethodType { EBIRR CARD WALLET + + @@schema("passenger") } enum PaymentIntentStatus { @@ -64,11 +113,16 @@ enum PaymentIntentStatus { SUCCEEDED FAILED CANCELLED + REFUNDED + + @@schema("passenger") } enum WalletLedgerType { CREDIT DEBIT + + @@schema("passenger") } enum NotificationCategory { @@ -77,6 +131,8 @@ enum NotificationCategory { DISRUPTION PROMOTION SYSTEM + + @@schema("passenger") } enum StopStatus { @@ -84,18 +140,24 @@ enum StopStatus { APPROACHING CURRENT UPCOMING + + @@schema("passenger") } enum SupportConversationStatus { OPEN RESOLVED CLOSED + + @@schema("passenger") } enum SupportSender { USER BOT AGENT + + @@schema("passenger") } enum LoyaltyTier { @@ -103,6 +165,8 @@ enum LoyaltyTier { SILVER GOLD PLATINUM + + @@schema("passenger") } enum LoyaltyLedgerReason { @@ -111,6 +175,8 @@ enum LoyaltyLedgerReason { PROMO_BONUS MANUAL_ADJUSTMENT EXPIRY + + @@schema("passenger") } enum FoodOrderStatus { @@ -119,12 +185,16 @@ enum FoodOrderStatus { READY DELIVERED CANCELLED + + @@schema("passenger") } enum DevicePlatform { IOS ANDROID WEB + + @@schema("passenger") } model User { @@ -134,12 +204,25 @@ model User { fullName String passwordHash String role UserRole @default(PASSENGER) + nationality String? + nationalityCode String? + passportNumber String? + nationalId String? + failedLoginAttempts Int @default(0) + lockedUntil DateTime? + blockedUntil DateTime? + lastLoginAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt passenger Passenger? + agent Agent? sessions Session[] devices Device[] preferences UserPreferences? + auditLogs AuditLog[] + fraudAlerts FraudAlert[] + + @@schema("passenger") } model Session { @@ -147,13 +230,20 @@ model Session { userId String token String @unique expiresAt DateTime + ipAddress String? + userAgent String? + lastActivityAt DateTime @default(now()) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@schema("passenger") } model Passenger { id String @id @default(uuid()) userId String @unique + defaultTravelerProfileId String? + preferredLanguage String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) bookings Booking[] @@ -162,6 +252,9 @@ model Passenger { notifications Notification[] travelerProfiles TravelerProfile[] savedRoutes SavedRoute[] + @@index([userId]) + + @@schema("passenger") } model TravelerProfile { @@ -174,6 +267,8 @@ model TravelerProfile { notes String? createdAt DateTime @default(now()) passenger Passenger @relation(fields: [passengerId], references: [id]) + + @@schema("passenger") } model Station { @@ -181,146 +276,240 @@ model Station { code String @unique name String city String + countryCode String? + isOperational Boolean @default(true) timezone String @default("Africa/Addis_Ababa") lat Decimal @db.Decimal(9, 6) lng Decimal @db.Decimal(9, 6) - originTrips Trip[] @relation("OriginTrips") - destinationTrips Trip[] @relation("DestinationTrips") - stopTimes TripStopTime[] - crowdSignals StationCrowdSignal[] + originSchedules TrainSchedule[] @relation("OriginTrips") + destinationSchedules TrainSchedule[] @relation("DestinationTrips") + stopTimes TripStopTime[] + crowdSignals StationCrowdSignal[] + @@index([city, countryCode]) + + @@schema("passenger") } -model TrainService { - id String @id @default(uuid()) - number String @unique - name String - operatorId String @default("op_edr") - trips Trip[] +model Train { + id String @id @default(uuid()) + number String @unique + name String + operatorId String @default("op_edr") + operatorName String? + description String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + schedules TrainSchedule[] + + @@schema("passenger") } -model Trip { - id String @id @default(uuid()) - serviceId String +model TrainSchedule { + id String @id @default(uuid()) + trainId String + routeId String? originStationId String destinationStationId String departureAt DateTime arrivalAt DateTime durationMinutes Int - status TripStatus @default(SCHEDULED) - stopsCount Int @default(0) - onTimePercent Int @default(100) - carbonRating String @default("A") - service TrainService @relation(fields: [serviceId], references: [id]) - originStation Station @relation("OriginTrips", fields: [originStationId], references: [id]) - destinationStation Station @relation("DestinationTrips", fields: [destinationStationId], references: [id]) - coaches Coach[] + status TripStatus @default(SCHEDULED) + stopsCount Int @default(0) + reservedCount Int @default(0) + onTimePercent Int @default(100) + carbonRating String @default("A") + notes String? + train Train @relation(fields: [trainId], references: [id]) + originStation Station @relation("OriginTrips", fields: [originStationId], references: [id]) + destinationStation Station @relation("DestinationTrips", fields: [destinationStationId], references: [id]) + coachAssignments CoachAssignment[] bookings Booking[] stopTimes TripStopTime[] liveStatus TripLiveStatus? menuItems MenuItem[] + journeySegments JourneySegment[] + @@index([departureAt, originStationId]) + + @@schema("passenger") } model TripStopTime { - id String @id @default(uuid()) - tripId String + id String @id @default(uuid()) + scheduleId String stationId String sequence Int plannedArrivalAt DateTime? plannedDepartureAt DateTime? actualArrivalAt DateTime? - status StopStatus @default(UPCOMING) - trip Trip @relation(fields: [tripId], references: [id]) - station Station @relation(fields: [stationId], references: [id]) - @@unique([tripId, sequence]) + status StopStatus @default(UPCOMING) + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) + station Station @relation(fields: [stationId], references: [id]) + @@unique([scheduleId, sequence]) + + @@schema("passenger") } model TripLiveStatus { - id String @id @default(uuid()) - tripId String @unique + id String @id @default(uuid()) + scheduleId String @unique state String currentLocationLabel String? - progressPercent Int @default(0) - delayMinutes Int @default(0) + progressPercent Int @default(0) + delayMinutes Int @default(0) currentSpeedKph Int? platformLabel String? - updatedAt DateTime @updatedAt - trip Trip @relation(fields: [tripId], references: [id]) + updatedAt DateTime @updatedAt + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) + + @@schema("passenger") } model Coach { - id String @id @default(uuid()) - tripId String - label String - serviceClass ServiceClass - trip Trip @relation(fields: [tripId], references: [id]) - seats Seat[] - @@unique([tripId, label]) + id String @id @default(uuid()) + coachNumber String @unique + label String + seatClassId String + coachType String? + mode String @default("seat") // 'seat', 'bed', 'convertible' + seatArrangement String? + bedArrangement String? + amenities Json? + totalUnits Int @default(0) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + seatClass SeatClass @relation(fields: [seatClassId], references: [id]) + seats Seat[] + assignments CoachAssignment[] + + @@schema("passenger") +} + +model CoachAssignment { + id String @id @default(uuid()) + scheduleId String + coachId String + positionNumber Int + isOperational Boolean @default(true) + createdAt DateTime @default(now()) + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) + coach Coach @relation(fields: [coachId], references: [id]) + @@unique([scheduleId, positionNumber]) + @@index([scheduleId]) + + @@schema("passenger") } model Seat { - id String @id @default(uuid()) - coachId String - row Int - col String - label String - kind SeatKind @default(STANDARD) - status SeatStatus @default(AVAILABLE) - heldUntil DateTime? + id String @id @default(uuid()) + coachId String + row Int + col String + label String + seatNumber String? + kind SeatKind @default(STANDARD) + status SeatStatus @default(AVAILABLE) + heldUntil DateTime? + isWindow Boolean @default(false) + isAisle Boolean @default(false) + bedPosition String? // 'lower', 'middle', 'upper' + premiumFeeMinor Int @default(0) + eligibility String? coach Coach @relation(fields: [coachId], references: [id]) bookingSeats BookingSeat[] + blocks SeatBlock[] @@unique([coachId, row, col]) + @@unique([coachId, seatNumber]) + + @@schema("passenger") } model SeatHold { id String @id @default(uuid()) - tripId String + scheduleId String seatIds String[] fareQuoteId String? passengerId String + createdBy String? expiresAt DateTime createdAt DateTime @default(now()) + @@index([expiresAt]) + + @@schema("passenger") } model FareRule { - id String @id @default(uuid()) + id String @id @default(uuid()) tripId String? route String? - serviceClass ServiceClass + seatClassId String baseFareMinor Int + seatClass SeatClass @relation(fields: [seatClassId], references: [id]) currency String @default("ETB") refundable Boolean @default(true) validFrom DateTime validUntil DateTime? createdAt DateTime @default(now()) + + @@schema("passenger") } model Booking { - id String @id @default(uuid()) - bookingRef String @unique - passengerId String - tripId String - status BookingStatus @default(DRAFT) - currency String @default("ETB") - totalMinor Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + bookingRef String @unique + passengerId String + scheduleId String + status BookingStatus @default(DRAFT) + currency String @default("ETB") + totalMinor Int + adultCount Int @default(1) + childCount Int @default(0) + displayCurrency Currency? + displayTotalMinor Int? + bookingType String @default("ONE_WAY") + userAgent String? + source String @default("WEB") + promoCode String? + paidAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt passenger Passenger @relation(fields: [passengerId], references: [id]) - trip Trip @relation(fields: [tripId], references: [id]) + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) seats BookingSeat[] paymentIntent PaymentIntent? ticket Ticket? foodOrders FoodOrder[] + agentBooking AgentBooking? + modifications BookingModification[] + cancellation BookingCancellation? + baggage BaggageBooking[] + @@index([passengerId, status]) + + @@schema("passenger") } model BookingSeat { - id String @id @default(uuid()) - bookingId String - seatId String - passengerName String - idDocumentType String? - idDocumentNumber String? + id String @id @default(uuid()) + bookingId String + seatId String + passengerName String + dateOfBirth DateTime? + passengerCategory PassengerCategory @default(ADULT) + idDocumentType IdDocumentType? + idDocumentNumber String? + passportNumber String? + passportCountry String? + verifaydaVerified Boolean @default(false) + verifaydaData Json? + seatLabelSnapshot String? + fareMinor Int? + displayCurrency Currency? + displayFareMinor Int? booking Booking @relation(fields: [bookingId], references: [id]) seat Seat @relation(fields: [seatId], references: [id]) + + @@schema("passenger") } model PaymentMethod { @@ -329,22 +518,73 @@ model PaymentMethod { type PaymentMethodType displayName String maskedHint String? + providerId String? isDefault Boolean @default(false) createdAt DateTime @default(now()) + @@index([userId, isDefault]) + + @@schema("passenger") } model PaymentIntent { - id String @id @default(uuid()) - bookingId String @unique - amountMinor Int - currency String @default("ETB") - method PaymentMethodType - status PaymentIntentStatus @default(REQUIRES_ACTION) - providerRef String? - clientAction Json? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - booking Booking @relation(fields: [bookingId], references: [id]) + id String @id @default(uuid()) + bookingId String @unique + amountMinor Int + currency String @default("ETB") + method PaymentMethodType + provider String? + status PaymentIntentStatus @default(REQUIRES_ACTION) + providerRef String? + clientAction Json? + merchantOrderId String? @unique + providerOrderId String? + providerTxnId String? + rawInitiation Json? + paidAt DateTime? + refundedAt DateTime? + captureMethod String? + failureCode String? + failureMessage String? + expiresAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + booking Booking @relation(fields: [bookingId], references: [id]) + refunds PaymentRefund[] + @@index([providerOrderId]) + @@index([providerTxnId]) + + @@schema("passenger") +} + +model PaymentWebhookEvent { + id String @id @default(uuid()) + provider PaymentMethodType + externalEventId String + merchantOrderId String? + providerTxnId String? + signatureValid Boolean + status String + payload Json + receivedAt DateTime @default(now()) + processedAt DateTime? + processingError String? + @@unique([provider, externalEventId]) + @@index([merchantOrderId]) + + @@schema("passenger") +} + +model PaymentRefund { + id String @id @default(uuid()) + paymentIntentId String + amountMinor Int + reason String? + providerRefundId String? + status String + createdAt DateTime @default(now()) + paymentIntent PaymentIntent @relation(fields: [paymentIntentId], references: [id]) + + @@schema("passenger") } model Ticket { @@ -353,21 +593,31 @@ model Ticket { bookingRef String status String @default("CONFIRMED") qrPayload String + barcodePayload String? + pdfUrl String? + deliveryChannel String @default("EMAIL") issuedAt DateTime @default(now()) validatedAt DateTime? validatorId String? booking Booking @relation(fields: [bookingId], references: [id]) + validationLogs GateValidationLog[] + + @@schema("passenger") } model LoyaltyAccount { id String @id @default(uuid()) passengerId String @unique pointsBalance Int @default(0) + lifetimePoints Int @default(0) tier LoyaltyTier @default(BRONZE) + tierUpdatedAt DateTime? updatedAt DateTime @updatedAt passenger Passenger @relation(fields: [passengerId], references: [id]) ledger LoyaltyLedgerEntry[] rewards LoyaltyReward[] + + @@schema("passenger") } model LoyaltyLedgerEntry { @@ -379,6 +629,8 @@ model LoyaltyLedgerEntry { balanceAfter Int createdAt DateTime @default(now()) account LoyaltyAccount @relation(fields: [accountId], references: [id]) + + @@schema("passenger") } model LoyaltyReward { @@ -389,16 +641,23 @@ model LoyaltyReward { available Boolean @default(true) description String? account LoyaltyAccount @relation(fields: [accountId], references: [id]) + + @@schema("passenger") } model WalletAccount { id String @id @default(uuid()) passengerId String @unique balanceMinor Int @default(0) + status String @default("ACTIVE") + holdMinor Int @default(0) currency String @default("ETB") updatedAt DateTime @updatedAt passenger Passenger @relation(fields: [passengerId], references: [id]) ledger WalletLedgerEntry[] + @@index([passengerId]) + + @@schema("passenger") } model WalletLedgerEntry { @@ -411,6 +670,8 @@ model WalletLedgerEntry { relatedBookingId String? createdAt DateTime @default(now()) wallet WalletAccount @relation(fields: [walletId], references: [id]) + + @@schema("passenger") } model Notification { @@ -424,6 +685,8 @@ model Notification { metadata Json? createdAt DateTime @default(now()) passenger Passenger @relation(fields: [passengerId], references: [id]) + + @@schema("passenger") } model Promotion { @@ -438,6 +701,8 @@ model Promotion { deepLink String? active Boolean @default(true) createdAt DateTime @default(now()) + + @@schema("passenger") } model StationCrowdSignal { @@ -446,8 +711,12 @@ model StationCrowdSignal { level String label String statusLabel String + confidence Int? + observedAt DateTime? updatedAt DateTime @updatedAt station Station @relation(fields: [stationId], references: [id]) + + @@schema("passenger") } model WeatherAlert { @@ -458,24 +727,31 @@ model WeatherAlert { message String validUntil DateTime createdAt DateTime @default(now()) + + @@schema("passenger") } model MenuCategory { id String @id @default(uuid()) name String items MenuItem[] + + @@schema("passenger") } model MenuItem { id String @id @default(uuid()) - tripId String + scheduleId String categoryId String name String priceMinor Int currency String @default("ETB") available Boolean @default(true) - trip Trip @relation(fields: [tripId], references: [id]) - category MenuCategory @relation(fields: [categoryId], references: [id]) + availableUntil DateTime? + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) + category MenuCategory @relation(fields: [categoryId], references: [id]) + + @@schema("passenger") } model FoodOrder { @@ -484,9 +760,13 @@ model FoodOrder { status FoodOrderStatus @default(PENDING) totalMinor Int currency String @default("ETB") + specialInstructions String? + estimatedReadyAt DateTime? createdAt DateTime @default(now()) booking Booking @relation(fields: [bookingId], references: [id]) items FoodOrderItem[] + + @@schema("passenger") } model FoodOrderItem { @@ -495,8 +775,11 @@ model FoodOrderItem { menuItemId String name String quantity Int + unitPriceMinor Int? lineTotalMinor Int order FoodOrder @relation(fields: [orderId], references: [id]) + + @@schema("passenger") } model FaqCategory { @@ -504,6 +787,8 @@ model FaqCategory { title String iconKey String? articles FaqArticle[] + + @@schema("passenger") } model FaqArticle { @@ -513,14 +798,19 @@ model FaqArticle { answerMarkdown String rank Int @default(0) category FaqCategory @relation(fields: [categoryId], references: [id]) + + @@schema("passenger") } model SupportConversation { id String @id @default(uuid()) userId String + assignedAgentId String? status SupportConversationStatus @default(OPEN) createdAt DateTime @default(now()) messages SupportMessage[] + + @@schema("passenger") } model SupportMessage { @@ -528,8 +818,11 @@ model SupportMessage { conversationId String sender SupportSender text String + attachments Json? createdAt DateTime @default(now()) conversation SupportConversation @relation(fields: [conversationId], references: [id]) + + @@schema("passenger") } model UserPreferences { @@ -546,7 +839,10 @@ model UserPreferences { dataSharing Boolean @default(false) locale String @default("en") darkMode Boolean @default(false) + language String @default("en") user User @relation(fields: [userId], references: [id]) + + @@schema("passenger") } model Device { @@ -558,6 +854,8 @@ model Device { trusted Boolean @default(false) lastSeenAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) + + @@schema("passenger") } model SavedRoute { @@ -570,4 +868,356 @@ model SavedRoute { tripCount Int @default(0) createdAt DateTime @default(now()) passenger Passenger @relation(fields: [passengerId], references: [id]) + + @@schema("passenger") +} + +model Journey { + id String @id @default(uuid()) + passengerId String + status String + totalMinor Int + currency String @default("ETB") + createdAt DateTime @default(now()) + journeySegments JourneySegment[] + + @@schema("passenger") +} + +model JourneySegment { + id String @id @default(uuid()) + journeyId String + scheduleId String + segmentOrder Int + seatId String? + coachId String? + departureStationId String + arrivalStationId String + journey Journey @relation(fields: [journeyId], references: [id]) + schedule TrainSchedule @relation(fields: [scheduleId], references: [id]) + + @@schema("passenger") +} + +model OtpCode { + id String @id @default(uuid()) + userId String? + email String? + phone String? + code String + purpose String + expiresAt DateTime + verified Boolean @default(false) + createdAt DateTime @default(now()) + @@index([email, phone]) + + @@schema("passenger") +} + +model PasswordResetToken { + id String @id @default(uuid()) + userId String + token String @unique + expiresAt DateTime + used Boolean @default(false) + createdAt DateTime @default(now()) + @@index([userId]) + + @@schema("passenger") +} + +model Route { + id String @id @default(uuid()) + code String @unique + name String + description String? + effectiveFrom DateTime + effectiveUntil DateTime? + active Boolean @default(true) + createdAt DateTime @default(now()) + stops RouteStop[] + fareRules RouteFareRule[] + + @@schema("passenger") +} + +model RouteStop { + id String @id @default(uuid()) + routeId String + stationId String + sequence Int + distanceKm Int? + createdAt DateTime @default(now()) + route Route @relation(fields: [routeId], references: [id], onDelete: Cascade) + @@unique([routeId, sequence]) + @@index([routeId, stationId]) + + @@schema("passenger") +} + +model RouteFareRule { + id String @id @default(uuid()) + routeId String + seatClassId String + passengerCategory PassengerCategory @default(ADULT) + baseFareMinor Int + discountPercent Int? + taxPercent Int? + surchargeMinor Int? + currency String @default("ETB") + validFrom DateTime + validUntil DateTime? + createdAt DateTime @default(now()) + route Route @relation(fields: [routeId], references: [id], onDelete: Cascade) + seatClass SeatClass @relation(fields: [seatClassId], references: [id]) + @@index([routeId, seatClassId]) + + @@schema("passenger") +} + +model Agent { + id String @id @default(uuid()) + userId String @unique + agentCode String @unique + stationId String? + commissionRate Int @default(5) + active Boolean @default(true) + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id]) + bookings AgentBooking[] + shifts AgentShift[] + commissions AgentCommission[] + + @@schema("passenger") +} + +model AgentBooking { + id String @id @default(uuid()) + agentId String + bookingId String @unique + paymentMethod String + cashReceived Int? + changeGiven Int? + paperTicket Boolean @default(false) + createdAt DateTime @default(now()) + agent Agent @relation(fields: [agentId], references: [id]) + booking Booking @relation(fields: [bookingId], references: [id]) + + @@schema("passenger") +} + +model AgentShift { + id String @id @default(uuid()) + agentId String + openedAt DateTime @default(now()) + closedAt DateTime? + openingBalance Int @default(0) + closingBalance Int? + reconciled Boolean @default(false) + notes String? + agent Agent @relation(fields: [agentId], references: [id]) + @@index([agentId, openedAt]) + + @@schema("passenger") +} + +model AgentCommission { + id String @id @default(uuid()) + agentId String + bookingId String + amountMinor Int + rate Int + paidAt DateTime? + createdAt DateTime @default(now()) + agent Agent @relation(fields: [agentId], references: [id]) + @@index([agentId, paidAt]) + + @@schema("passenger") +} + +model BookingModification { + id String @id @default(uuid()) + bookingId String + modifiedBy String + modificationType String + oldData Json + newData Json + fareAdjustment Int @default(0) + reason String? + createdAt DateTime @default(now()) + booking Booking @relation(fields: [bookingId], references: [id]) + @@index([bookingId]) + + @@schema("passenger") +} + +model BookingCancellation { + id String @id @default(uuid()) + bookingId String @unique + cancelledBy String + reason String? + refundAmount Int + refundMethod String + refundStatus String + processedAt DateTime? + createdAt DateTime @default(now()) + booking Booking @relation(fields: [bookingId], references: [id]) + + @@schema("passenger") +} + +model GateValidationLog { + id String @id @default(uuid()) + ticketId String + validatorId String + gateId String? + status String + reason String? + validatedAt DateTime @default(now()) + ticket Ticket @relation(fields: [ticketId], references: [id]) + @@index([ticketId]) + @@index([validatorId]) + + @@schema("passenger") +} + +model BaggageAllowance { + id String @id @default(uuid()) + seatClassId String + maxWeightKg Int + maxPiecesCount Int + excessFeePerKg Int + currency String @default("ETB") + createdAt DateTime @default(now()) + + @@schema("passenger") +} + +model BaggageBooking { + id String @id @default(uuid()) + bookingId String + weightKg Int + piecesCount Int + excessFeeMinor Int @default(0) + paid Boolean @default(false) + createdAt DateTime @default(now()) + booking Booking @relation(fields: [bookingId], references: [id]) + @@index([bookingId]) + + @@schema("passenger") +} + +model AuditLog { + id String @id @default(uuid()) + userId String? + action String + entityType String + entityId String? + oldData Json? + newData Json? + ipAddress String? + userAgent String? + createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id]) + @@index([userId, createdAt]) + @@index([entityType, entityId]) + + @@schema("passenger") +} + +model NotificationTemplate { + id String @id @default(uuid()) + code String @unique + channel String + subject String? + bodyTemplate String + active Boolean @default(true) + createdAt DateTime @default(now()) + + @@schema("passenger") +} + +model SeatBlock { + id String @id @default(uuid()) + seatId String + reason String + blockedBy String + approvedBy String? + blockedAt DateTime @default(now()) + unblockAt DateTime? + seat Seat @relation(fields: [seatId], references: [id]) + @@index([seatId]) + + @@schema("passenger") +} + +model OperationalReport { + id String @id @default(uuid()) + reportType String + dateFrom DateTime + dateTo DateTime + data Json + generatedBy String? + createdAt DateTime @default(now()) + @@index([reportType, dateFrom]) + + @@schema("passenger") +} + +model FraudRule { + id String @id @default(uuid()) + type String @unique + enabled Boolean @default(true) + threshold Float + config Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@schema("passenger") +} + +model FraudAlert { + id String @id @default(uuid()) + userId String + eventType String + triggeredRules String[] + context Json + severity String @default("MEDIUM") + acknowledged Boolean @default(false) + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + @@index([userId, createdAt]) + @@index([acknowledged]) + + @@schema("passenger") +} + +model CurrencyExchangeRate { + id String @id @default(uuid()) + fromCurrency Currency + toCurrency Currency + rate Decimal @db.Decimal(18, 6) + effectiveDate DateTime @default(now()) + source String @default("MANUAL") + createdAt DateTime @default(now()) + @@unique([fromCurrency, toCurrency, effectiveDate]) + @@index([fromCurrency, toCurrency]) + + @@schema("passenger") +} + +model VerifaydaVerification { + id String @id @default(uuid()) + bookingId String? + nationalId String + requestPayload Json + responsePayload Json? + verified Boolean @default(false) + failureReason String? + verifiedAt DateTime? + createdAt DateTime @default(now()) + @@index([nationalId]) + @@index([bookingId]) + + @@schema("passenger") } diff --git a/apps/edr-passenger-api/prisma/seed.d.ts b/apps/edr-passenger-api/prisma/seed.d.ts deleted file mode 100644 index 0986b1c3a..000000000 --- a/apps/edr-passenger-api/prisma/seed.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=seed.d.ts.map \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/seed.d.ts.map b/apps/edr-passenger-api/prisma/seed.d.ts.map deleted file mode 100644 index c1a0e0b3d..000000000 --- a/apps/edr-passenger-api/prisma/seed.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["seed.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/seed.js b/apps/edr-passenger-api/prisma/seed.js deleted file mode 100644 index 924e06d85..000000000 --- a/apps/edr-passenger-api/prisma/seed.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -const client_1 = require("@prisma/client"); -const bcrypt = __importStar(require("bcrypt")); -const prisma = new client_1.PrismaClient(); -async function main() { - const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa', city: 'Addis Ababa', lat: 9.0054, lng: 38.7636 } }); - const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', lat: 9.5931, lng: 41.8661 } }); - const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } }); - const service = await prisma.trainService.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301' } }); - const trip = await prisma.trip.create({ - data: { serviceId: service.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-05-11T08:30:00Z'), arrivalAt: new Date('2026-05-11T20:00:00Z'), durationMinutes: 690, stopsCount: 1 }, - }); - for (const [label, cls] of [['A', 'ECONOMY'], ['B', 'BUSINESS']]) { - const coach = await prisma.coach.create({ data: { tripId: trip.id, label, serviceClass: cls } }); - const seats = []; - for (let row = 1; row <= 10; row++) { - for (const col of ['A', 'B', 'C', 'D']) - seats.push({ coachId: coach.id, row, col, label: `${row}${col}` }); - } - await prisma.seat.createMany({ data: seats }); - } - await prisma.fareRule.create({ data: { tripId: trip.id, serviceClass: 'ECONOMY', baseFareMinor: 45000, validFrom: new Date('2026-01-01') } }); - const hash = await bcrypt.hash('password123', 10); - const user = await prisma.user.upsert({ where: { email: 'kelemu@email.com' }, update: {}, create: { fullName: 'Kelemu Ketsela', email: 'kelemu@email.com', phone: '+251912345678', passwordHash: hash } }); - let passenger = await prisma.passenger.findUnique({ where: { userId: user.id } }); - if (!passenger) { - passenger = await prisma.passenger.create({ data: { userId: user.id } }); - await prisma.loyaltyAccount.create({ data: { passengerId: passenger.id, pointsBalance: 2450, tier: 'SILVER' } }); - await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 125000 } }); - } - await prisma.userPreferences.upsert({ where: { userId: user.id }, update: {}, create: { userId: user.id } }); - await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: { passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' }, create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' } }); - await prisma.promotion.upsert({ where: { code: 'WEEKEND15' }, update: {}, create: { title: 'Weekend Sale', code: 'WEEKEND15', percentOff: 15, validUntil: new Date('2026-12-31') } }); - const faqCat = await prisma.faqCategory.create({ data: { title: 'Booking & Tickets', iconKey: 'description_outlined' } }); - await prisma.faqArticle.create({ data: { categoryId: faqCat.id, question: 'How do I book a train ticket?', answerMarkdown: 'Open Search, pick stations and date, select seats, and proceed to payment.', rank: 1 } }); - console.log('โœ… Seed complete'); -} -main().catch(console.error).finally(() => prisma.$disconnect()); -//# sourceMappingURL=seed.js.map \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/seed.js.map b/apps/edr-passenger-api/prisma/seed.js.map deleted file mode 100644 index bcc581885..000000000 --- a/apps/edr-passenger-api/prisma/seed.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"seed.js","sourceRoot":"","sources":["seed.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA8C;AAC9C,+CAAiC;AAEjC,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAChL,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAC/K,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAE3M,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IAE3I,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,EAAE,oBAAoB,EAAE,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE;KAC/N,CAAC,CAAC;IAEH,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,CAAU,EAAE,CAAC;QAC1E,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACjG,MAAM,KAAK,GAAG,EAAE,CAAC;QACjB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;YACnC,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;IAE9I,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC3M,IAAI,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClF,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QACjH,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAE7G,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAEjT,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;IAEtL,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAC1H,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,+BAA+B,EAAE,cAAc,EAAE,4EAA4E,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEtN,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/apps/edr-passenger-api/prisma/seed.ts b/apps/edr-passenger-api/prisma/seed.ts index f2bbb1332..4e8c1a68f 100644 --- a/apps/edr-passenger-api/prisma/seed.ts +++ b/apps/edr-passenger-api/prisma/seed.ts @@ -1,48 +1,184 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient, SeatKind } from '@prisma/client'; import * as bcrypt from 'bcrypt'; const prisma = new PrismaClient(); async function main() { - const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa', city: 'Addis Ababa', lat: 9.0054, lng: 38.7636 } }); - const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', lat: 9.5931, lng: 41.8661 } }); - const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } }); + console.log('๐ŸŒฑ Starting comprehensive seed...'); - const service = await prisma.trainService.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301' } }); + // Stations + const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa Central', city: 'Addis Ababa', countryCode: 'ET', lat: 9.0054, lng: 38.7636 } }); + const sebeta = await prisma.station.upsert({ where: { code: 'SBT' }, update: {}, create: { code: 'SBT', name: 'Sebeta', city: 'Sebeta', countryCode: 'ET', lat: 8.9167, lng: 38.6167 } }); + const adama = await prisma.station.upsert({ where: { code: 'ADM' }, update: {}, create: { code: 'ADM', name: 'Adama', city: 'Adama', countryCode: 'ET', lat: 8.5400, lng: 39.2675 } }); + const awash = await prisma.station.upsert({ where: { code: 'AWS' }, update: {}, create: { code: 'AWS', name: 'Awash', city: 'Awash', countryCode: 'ET', lat: 8.9833, lng: 40.1667 } }); + const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', countryCode: 'ET', lat: 9.5931, lng: 41.8661 } }); + const aysha = await prisma.station.upsert({ where: { code: 'AYS' }, update: {}, create: { code: 'AYS', name: 'Aysha', city: 'Aysha', countryCode: 'ET', lat: 11.5500, lng: 42.7167 } }); + const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } }); - const trip = await prisma.trip.create({ - data: { serviceId: service.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-05-11T08:30:00Z'), arrivalAt: new Date('2026-05-11T20:00:00Z'), durationMinutes: 690, stopsCount: 1 }, - }); + // Seat Classes + const scEconomyRegular = await prisma.seatClass.upsert({ where: { name: 'Economy Regular' }, update: {}, create: { name: 'Economy Regular', description: 'Standard economy seating', basePrice: 45000, isActive: true } }); + const scEconomyBed = await prisma.seatClass.upsert({ where: { name: 'Economy Bed' }, update: {}, create: { name: 'Economy Bed', description: 'Economy bed lower berth', basePrice: 65000, isActive: true } }); + const scVipBed = await prisma.seatClass.upsert({ where: { name: 'VIP Bed' }, update: {}, create: { name: 'VIP Bed', description: 'First class VIP bed', basePrice: 95000, isActive: true } }); - for (const [label, cls] of [['A', 'ECONOMY'], ['B', 'BUSINESS']] as const) { - const coach = await prisma.coach.create({ data: { tripId: trip.id, label, serviceClass: cls } }); - const seats = []; - for (let row = 1; row <= 10; row++) { - for (const col of ['A', 'B', 'C', 'D']) seats.push({ coachId: coach.id, row, col, label: `${row}${col}` }); + // Trains (logical services) + const train301 = await prisma.train.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301', description: 'Addis-Djibouti Express' } }); + const train302 = await prisma.train.upsert({ where: { number: '302' }, update: {}, create: { number: '302', name: 'Express 302', description: 'Djibouti-Addis Express' } }); + + // Physical Coaches (reusable) + const coachA1 = await prisma.coach.upsert({ where: { coachNumber: 'C-A1' }, update: {}, create: { coachNumber: 'C-A1', label: 'A', seatClassId: scEconomyRegular.id, mode: 'seat', totalUnits: 60 } }); + const coachB1 = await prisma.coach.upsert({ where: { coachNumber: 'C-B1' }, update: {}, create: { coachNumber: 'C-B1', label: 'B', seatClassId: scEconomyBed.id, mode: 'bed', totalUnits: 40 } }); + const coachC1 = await prisma.coach.upsert({ where: { coachNumber: 'C-C1' }, update: {}, create: { coachNumber: 'C-C1', label: 'C', seatClassId: scVipBed.id, mode: 'bed', totalUnits: 20 } }); + const coachA2 = await prisma.coach.upsert({ where: { coachNumber: 'C-A2' }, update: {}, create: { coachNumber: 'C-A2', label: 'A', seatClassId: scEconomyRegular.id, mode: 'seat', totalUnits: 60 } }); + const coachB2 = await prisma.coach.upsert({ where: { coachNumber: 'C-B2' }, update: {}, create: { coachNumber: 'C-B2', label: 'B', seatClassId: scEconomyBed.id, mode: 'bed', totalUnits: 40 } }); + const coachC2 = await prisma.coach.upsert({ where: { coachNumber: 'C-C2' }, update: {}, create: { coachNumber: 'C-C2', label: 'C', seatClassId: scVipBed.id, mode: 'bed', totalUnits: 20 } }); + + // Create seats for each physical coach + for (const coach of [coachA1, coachB1, coachC1, coachA2, coachB2, coachC2]) { + const existingSeats = await prisma.seat.count({ where: { coachId: coach.id } }); + if (existingSeats === 0) { + const seats = []; + const rows = Math.ceil(coach.totalUnits / 4); + for (let row = 1; row <= rows; row++) { + for (const col of ['A', 'B', 'C', 'D']) { + if (seats.length >= coach.totalUnits) break; + seats.push({ coachId: coach.id, row, col, label: `${row}${col}`, seatNumber: `${coach.label}${row}${col}`, kind: (row === 1 && col === 'A' ? 'ACCESSIBLE' : 'STANDARD') as SeatKind }); + } + } + await prisma.seat.createMany({ data: seats }); } - await prisma.seat.createMany({ data: seats }); } - await prisma.fareRule.create({ data: { tripId: trip.id, serviceClass: 'ECONOMY', baseFareMinor: 45000, validFrom: new Date('2026-01-01') } }); + // Train Schedules โ€” delete dependents first to avoid FK violations + const existingScheduleIds = (await prisma.trainSchedule.findMany({ + where: { trainId: { in: [train301.id, train302.id] } }, + select: { id: true }, + })).map((s) => s.id); + if (existingScheduleIds.length > 0) { + await prisma.fareRule.deleteMany({ where: { tripId: { in: existingScheduleIds } } }); + await prisma.tripStopTime.deleteMany({ where: { scheduleId: { in: existingScheduleIds } } }); + await prisma.coachAssignment.deleteMany({ where: { scheduleId: { in: existingScheduleIds } } }); + await prisma.trainSchedule.deleteMany({ where: { id: { in: existingScheduleIds } } }); + } + const schedule1 = await prisma.trainSchedule.create({ + data: { trainId: train301.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-06-15T08:00:00Z'), arrivalAt: new Date('2026-06-15T20:00:00Z'), durationMinutes: 720, stopsCount: 6 }, + }); + const schedule2 = await prisma.trainSchedule.create({ + data: { trainId: train302.id, originStationId: djibouti.id, destinationStationId: addis.id, departureAt: new Date('2026-06-16T09:00:00Z'), arrivalAt: new Date('2026-06-16T21:30:00Z'), durationMinutes: 750, stopsCount: 5 }, + }); + const schedule3 = await prisma.trainSchedule.create({ + data: { trainId: train301.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-06-17T07:30:00Z'), arrivalAt: new Date('2026-06-17T19:45:00Z'), durationMinutes: 735, stopsCount: 5 }, + }); + const schedule4 = await prisma.trainSchedule.create({ + data: { trainId: train302.id, originStationId: djibouti.id, destinationStationId: addis.id, departureAt: new Date('2026-06-18T08:30:00Z'), arrivalAt: new Date('2026-06-18T21:00:00Z'), durationMinutes: 750, stopsCount: 5 }, + }); + // Assign coaches to schedules + await prisma.coachAssignment.createMany({ + data: [ + { scheduleId: schedule1.id, coachId: coachA1.id, positionNumber: 1 }, + { scheduleId: schedule1.id, coachId: coachB1.id, positionNumber: 2 }, + { scheduleId: schedule1.id, coachId: coachC1.id, positionNumber: 3 }, + { scheduleId: schedule2.id, coachId: coachA2.id, positionNumber: 1 }, + { scheduleId: schedule2.id, coachId: coachB2.id, positionNumber: 2 }, + { scheduleId: schedule2.id, coachId: coachC2.id, positionNumber: 3 }, + { scheduleId: schedule3.id, coachId: coachA1.id, positionNumber: 1 }, + { scheduleId: schedule3.id, coachId: coachB1.id, positionNumber: 2 }, + { scheduleId: schedule3.id, coachId: coachC1.id, positionNumber: 3 }, + { scheduleId: schedule4.id, coachId: coachA2.id, positionNumber: 1 }, + { scheduleId: schedule4.id, coachId: coachB2.id, positionNumber: 2 }, + { scheduleId: schedule4.id, coachId: coachC2.id, positionNumber: 3 }, + ], + skipDuplicates: true, + }); + + // Stop Times + await prisma.tripStopTime.createMany({ + data: [ + { scheduleId: schedule1.id, stationId: addis.id, sequence: 1, plannedDepartureAt: new Date('2026-06-15T08:00:00Z'), status: 'UPCOMING' }, + { scheduleId: schedule1.id, stationId: adama.id, sequence: 2, plannedArrivalAt: new Date('2026-06-15T09:30:00Z'), plannedDepartureAt: new Date('2026-06-15T09:45:00Z'), status: 'UPCOMING' }, + { scheduleId: schedule1.id, stationId: awash.id, sequence: 3, plannedArrivalAt: new Date('2026-06-15T11:30:00Z'), plannedDepartureAt: new Date('2026-06-15T11:45:00Z'), status: 'UPCOMING' }, + { scheduleId: schedule1.id, stationId: direDawa.id, sequence: 4, plannedArrivalAt: new Date('2026-06-15T15:00:00Z'), plannedDepartureAt: new Date('2026-06-15T15:20:00Z'), status: 'UPCOMING' }, + { scheduleId: schedule1.id, stationId: aysha.id, sequence: 5, plannedArrivalAt: new Date('2026-06-15T18:00:00Z'), plannedDepartureAt: new Date('2026-06-15T18:10:00Z'), status: 'UPCOMING' }, + { scheduleId: schedule1.id, stationId: djibouti.id, sequence: 6, plannedArrivalAt: new Date('2026-06-15T20:00:00Z'), status: 'UPCOMING' }, + ], + }); + + // Fare Rules + for (const schedule of [schedule1, schedule2, schedule3, schedule4]) { + await prisma.fareRule.createMany({ + data: [ + { tripId: schedule.id, seatClassId: scEconomyRegular.id, baseFareMinor: 45000, validFrom: new Date('2026-01-01'), refundable: true }, + { tripId: schedule.id, seatClassId: scEconomyBed.id, baseFareMinor: 65000, validFrom: new Date('2026-01-01'), refundable: true }, + { tripId: schedule.id, seatClassId: scVipBed.id, baseFareMinor: 95000, validFrom: new Date('2026-01-01'), refundable: true }, + ], + skipDuplicates: true, + }); + } + + // Users const hash = await bcrypt.hash('password123', 10); - const user = await prisma.user.upsert({ where: { email: 'kelemu@email.com' }, update: {}, create: { fullName: 'Kelemu Ketsela', email: 'kelemu@email.com', phone: '+251912345678', passwordHash: hash } }); - let passenger = await prisma.passenger.findUnique({ where: { userId: user.id } }); + const adminHash = await bcrypt.hash('admin123', 10); + const agentHash = await bcrypt.hash('agent123', 10); + + await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: { passwordHash: adminHash, role: 'ADMIN' }, create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: adminHash, role: 'ADMIN' } }); + const passengerUser = await prisma.user.upsert({ where: { email: 'kelemu@email.com' }, update: {}, create: { fullName: 'Kelemu Ketsela', email: 'kelemu@email.com', phone: '+251912345678', passwordHash: hash, nationality: 'Ethiopian', nationalId: 'ET123456789' } }); + let passenger = await prisma.passenger.findUnique({ where: { userId: passengerUser.id } }); if (!passenger) { - passenger = await prisma.passenger.create({ data: { userId: user.id } }); + passenger = await prisma.passenger.create({ data: { userId: passengerUser.id } }); await prisma.loyaltyAccount.create({ data: { passengerId: passenger.id, pointsBalance: 2450, tier: 'SILVER' } }); await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 125000 } }); } - await prisma.userPreferences.upsert({ where: { userId: user.id }, update: {}, create: { userId: user.id } }); + await prisma.userPreferences.upsert({ where: { userId: passengerUser.id }, update: {}, create: { userId: passengerUser.id, language: 'en' } }); - await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: { passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' }, create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' } }); + const agentUser = await prisma.user.upsert({ where: { email: 'agent@edr-platform.com' }, update: { passwordHash: agentHash, role: 'AGENT' }, create: { fullName: 'Agent Abebe', email: 'agent@edr-platform.com', phone: '+251911111111', passwordHash: agentHash, role: 'AGENT' } }); + await prisma.agent.upsert({ where: { userId: agentUser.id }, update: {}, create: { userId: agentUser.id, agentCode: 'AG001', stationId: addis.id, commissionRate: 5, active: true } }); - await prisma.promotion.upsert({ where: { code: 'WEEKEND15' }, update: {}, create: { title: 'Weekend Sale', code: 'WEEKEND15', percentOff: 15, validUntil: new Date('2026-12-31') } }); + // Baggage Allowance + await prisma.baggageAllowance.deleteMany({}); + await prisma.baggageAllowance.createMany({ + data: [ + { seatClassId: scEconomyRegular.id, maxWeightKg: 20, maxPiecesCount: 2, excessFeePerKg: 500 }, + { seatClassId: scEconomyBed.id, maxWeightKg: 25, maxPiecesCount: 2, excessFeePerKg: 450 }, + { seatClassId: scVipBed.id, maxWeightKg: 30, maxPiecesCount: 3, excessFeePerKg: 400 }, + ], + }); - const faqCat = await prisma.faqCategory.create({ data: { title: 'Booking & Tickets', iconKey: 'description_outlined' } }); - await prisma.faqArticle.create({ data: { categoryId: faqCat.id, question: 'How do I book a train ticket?', answerMarkdown: 'Open Search, pick stations and date, select seats, and proceed to payment.', rank: 1 } }); + // Notification Templates + await prisma.notificationTemplate.upsert({ where: { code: 'BOOKING_CONFIRMED' }, update: {}, create: { code: 'BOOKING_CONFIRMED', channel: 'EMAIL', subject: 'Booking Confirmed', bodyTemplate: 'Your booking {{bookingRef}} is confirmed for {{tripDate}}.', active: true } }); + await prisma.notificationTemplate.upsert({ where: { code: 'PAYMENT_SUCCESS' }, update: {}, create: { code: 'PAYMENT_SUCCESS', channel: 'SMS', bodyTemplate: 'Payment successful for {{bookingRef}}. Amount: {{amount}} ETB', active: true } }); - console.log('โœ… Seed complete'); + // Promotions + await prisma.promotion.upsert({ where: { code: 'WEEKEND15' }, update: {}, create: { title: 'Weekend Sale', subtitle: '15% off all trips', code: 'WEEKEND15', percentOff: 15, validUntil: new Date('2026-12-31'), ctaLabel: 'Book Now', active: true } }); + + // FAQ + await prisma.faqArticle.deleteMany({}); + await prisma.faqCategory.deleteMany({}); + const faqBooking = await prisma.faqCategory.create({ data: { title: 'Booking & Tickets', iconKey: 'confirmation_number' } }); + await prisma.faqArticle.createMany({ data: [{ categoryId: faqBooking.id, question: 'How do I book a train ticket?', answerMarkdown: 'Open Search, select origin and destination stations, choose date, select seats, and proceed to payment.', rank: 1 }] }); + + // Station Crowd Signals + await prisma.stationCrowdSignal.deleteMany({}); + await prisma.stationCrowdSignal.createMany({ data: [{ stationId: addis.id, level: 'MODERATE', label: 'Moderate', statusLabel: 'Normal operations' }, { stationId: djibouti.id, level: 'HIGH', label: 'High', statusLabel: 'Busy terminal' }] }); + + // Fraud Detection Rules + await prisma.fraudRule.upsert({ where: { type: 'VELOCITY' }, update: {}, create: { type: 'VELOCITY', enabled: true, threshold: 3, config: { windowMinutes: 60, action: 'FLAG' } } }); + + // Currency Exchange Rates + await prisma.currencyExchangeRate.deleteMany({}); + await prisma.currencyExchangeRate.createMany({ data: [{ fromCurrency: 'ETB', toCurrency: 'ETB', rate: 1.0, effectiveDate: new Date() }, { fromCurrency: 'ETB', toCurrency: 'USD', rate: 0.018, effectiveDate: new Date() }] }); + + console.log('โœ… Comprehensive seed complete'); + console.log('\n๐Ÿ“‹ Seed Summary:'); + console.log(' - 2 Trains (Express 301, Express 302)'); + console.log(' - 6 Physical Coaches (reusable across schedules)'); + console.log(' - 4 Train Schedules with coach assignments'); + console.log(' - 3 Seat Classes (Economy Regular, Economy Bed, VIP Bed)'); + console.log(' - 3 Users: Admin, Passenger (Silver tier + wallet), Agent'); + console.log('\n๐Ÿ”‘ Login Credentials:'); + console.log(' Admin: admin@edr-platform.com / admin123'); + console.log(' Passenger: kelemu@email.com / password123'); + console.log(' Agent: agent@edr-platform.com / agent123'); + console.log('\n๐Ÿš‚ Architecture: Train โ†’ TrainSchedule โ†” CoachAssignment โ†” Coach โ†’ Seat'); } main().catch(console.error).finally(() => prisma.$disconnect()); diff --git a/apps/edr-passenger-api/src/app.module.ts b/apps/edr-passenger-api/src/app.module.ts index 5b594f16e..7b2f460d3 100644 --- a/apps/edr-passenger-api/src/app.module.ts +++ b/apps/edr-passenger-api/src/app.module.ts @@ -1,10 +1,17 @@ -import { Module } from '@nestjs/common'; +import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ScheduleModule } from '@nestjs/schedule'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { PrismaModule } from './common/prisma.module'; +import { I18nModule } from './common/i18n/i18n.module'; +import { IamModule } from './common/iam.module'; +import { LocaleMiddleware } from './common/i18n/locale.middleware'; import appConfig from './config/app.config'; import dbConfig from './config/database.config'; +import telebirrConfig from './config/telebirr.config'; +import cbeConfig from './config/cbe.config'; +import ebirrConfig from './config/ebirr.config'; +import cardConfig from './config/card.config'; import { AuthModule } from './modules/auth/auth.module'; import { StationsModule } from './modules/stations/stations.module'; import { FleetModule } from './modules/fleet/fleet.module'; @@ -22,13 +29,23 @@ import { PromosModule } from './modules/promos/promos.module'; import { LiveModule } from './modules/live/live.module'; import { SupportModule } from './modules/support/support.module'; import { DashboardModule } from './modules/dashboard/dashboard.module'; +import { SegmentsModule } from './modules/segments/segments.module'; +import { AgentsModule } from './modules/agents/agents.module'; +import { ReportsModule } from './modules/reports/reports.module'; +import { FraudModule } from './modules/fraud/fraud.module'; +import { SeatClassesModule } from './modules/seat-classes/seat-classes.module'; @Module({ imports: [ - ConfigModule.forRoot({ isGlobal: true, load: [appConfig, dbConfig] }), + ConfigModule.forRoot({ + isGlobal: true, + load: [appConfig, dbConfig, telebirrConfig, cbeConfig, ebirrConfig, cardConfig], + }), ScheduleModule.forRoot(), EventEmitterModule.forRoot(), PrismaModule, + I18nModule, + IamModule, AuthModule, StationsModule, FleetModule, @@ -46,6 +63,15 @@ import { DashboardModule } from './modules/dashboard/dashboard.module'; LiveModule, SupportModule, DashboardModule, + SegmentsModule, + AgentsModule, + ReportsModule, + FraudModule, + SeatClassesModule, ], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(LocaleMiddleware).forRoutes('*'); + } +} diff --git a/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts b/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts index d1f73bf71..f02ce4abf 100644 --- a/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts +++ b/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts @@ -34,8 +34,9 @@ export class HttpExceptionFilter implements ExceptionFilter { if (status >= 500) { this.logger.error( `${request.method} ${request.url} -> ${status}`, - (exception as Error)?.stack, + exception instanceof Error ? exception.stack : JSON.stringify(exception), ); + console.error('Full error details:', exception); } else { this.logger.warn(`${request.method} ${request.url} -> ${status} ${message}`); } diff --git a/apps/edr-passenger-api/src/common/i18n/i18n.module.ts b/apps/edr-passenger-api/src/common/i18n/i18n.module.ts new file mode 100644 index 000000000..99d70185c --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/i18n.module.ts @@ -0,0 +1,9 @@ +import { Module, Global } from '@nestjs/common'; +import { I18nService } from './i18n.service'; + +@Global() +@Module({ + providers: [I18nService], + exports: [I18nService], +}) +export class I18nModule {} diff --git a/apps/edr-passenger-api/src/common/i18n/i18n.service.spec.ts b/apps/edr-passenger-api/src/common/i18n/i18n.service.spec.ts new file mode 100644 index 000000000..37c74c04b --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/i18n.service.spec.ts @@ -0,0 +1,49 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { I18nService } from './i18n.service'; + +describe('I18nService', () => { + let service: I18nService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [I18nService], + }).compile(); + + service = module.get(I18nService); + }); + + it('should translate English keys', () => { + expect(service.translate('common.welcome', 'en')).toBe('Welcome'); + expect(service.translate('booking.created', 'en')).toBe('Booking created successfully'); + }); + + it('should translate Amharic keys', () => { + expect(service.translate('common.welcome', 'am')).toBe('แŠฅแŠ•แŠณแŠ• แ‹ฐแˆ…แŠ“ แˆ˜แŒก'); + }); + + it('should translate French keys', () => { + expect(service.translate('common.welcome', 'fr')).toBe('Bienvenue'); + }); + + it('should translate Oromo keys', () => { + expect(service.translate('common.welcome', 'om')).toBe('Baga nagaan dhuftan'); + }); + + it('should fallback to English for unsupported locale', () => { + expect(service.translate('common.welcome', 'de')).toBe('Welcome'); + }); + + it('should return key if translation not found', () => { + expect(service.translate('nonexistent.key', 'en')).toBe('nonexistent.key'); + }); + + it('should interpolate parameters', () => { + const result = service.translate('common.welcome', 'en', { name: 'John' }); + expect(result).toBeDefined(); + }); + + it('should return supported locales', () => { + const locales = service.getSupportedLocales(); + expect(locales).toEqual(['en', 'am', 'fr', 'om']); + }); +}); diff --git a/apps/edr-passenger-api/src/common/i18n/i18n.service.ts b/apps/edr-passenger-api/src/common/i18n/i18n.service.ts new file mode 100644 index 000000000..9c3eee5fc --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/i18n.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; + +type TranslationMap = Record; + +@Injectable() +export class I18nService { + private translations: Map = new Map(); + private readonly supportedLocales = ['en', 'am', 'fr', 'om']; + private readonly defaultLocale = 'en'; + + constructor() { + this.loadTranslations(); + } + + private loadTranslations() { + for (const locale of this.supportedLocales) { + const filePath = path.join(__dirname, 'translations', `${locale}.json`); + try { + const content = fs.readFileSync(filePath, 'utf-8'); + this.translations.set(locale, JSON.parse(content)); + } catch (err) { + console.warn(`Failed to load translation file for locale: ${locale}`); + } + } + } + + translate(key: string, locale: string = this.defaultLocale, params?: Record): string { + const normalizedLocale = this.normalizeLocale(locale); + const translations = this.translations.get(normalizedLocale) || this.translations.get(this.defaultLocale); + + if (!translations) return key; + + const keys = key.split('.'); + let value: any = translations; + + for (const k of keys) { + value = value?.[k]; + if (value === undefined) return key; + } + + if (typeof value !== 'string') return key; + + if (params) { + return Object.entries(params).reduce( + (text, [param, val]) => text.replace(new RegExp(`{{${param}}}`, 'g'), val), + value + ); + } + + return value; + } + + private normalizeLocale(locale: string): string { + const normalized = locale.toLowerCase().split('-')[0]; + return this.supportedLocales.includes(normalized) ? normalized : this.defaultLocale; + } + + getSupportedLocales(): string[] { + return this.supportedLocales; + } +} diff --git a/apps/edr-passenger-api/src/common/i18n/locale.decorator.ts b/apps/edr-passenger-api/src/common/i18n/locale.decorator.ts new file mode 100644 index 000000000..388582795 --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/locale.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { LOCALE_KEY } from './locale.middleware'; + +export const Locale = createParamDecorator( + (data: unknown, ctx: ExecutionContext): string => { + const request = ctx.switchToHttp().getRequest(); + return request[LOCALE_KEY] || 'en'; + }, +); diff --git a/apps/edr-passenger-api/src/common/i18n/locale.middleware.ts b/apps/edr-passenger-api/src/common/i18n/locale.middleware.ts new file mode 100644 index 000000000..81bbc6990 --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/locale.middleware.ts @@ -0,0 +1,16 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; + +export const LOCALE_KEY = 'locale'; + +@Injectable() +export class LocaleMiddleware implements NestMiddleware { + use(req: any, res: any, next: () => void) { + const locale = + req.query.lang as string || + req.headers['accept-language']?.split(',')[0]?.split('-')[0] || + 'en'; + + req[LOCALE_KEY] = locale; + next(); + } +} diff --git a/apps/edr-passenger-api/src/common/i18n/translations/am.json b/apps/edr-passenger-api/src/common/i18n/translations/am.json new file mode 100644 index 000000000..16250494d --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/translations/am.json @@ -0,0 +1,23 @@ +{ + "common": { + "welcome": "แŠฅแŠ•แŠณแŠ• แ‹ฐแˆ…แŠ“ แˆ˜แŒก", + "error": "แˆตแˆ…แ‰ฐแ‰ต แ‰ฐแŠจแˆตแ‰ทแˆ", + "success": "แ‰ฐแˆณแŠญแ‰ทแˆ" + }, + "booking": { + "created": "แ‰ฆแ‰ณ แˆ›แˆตแ‹ซแ‹ แ‰ แ‰ฐแˆณแŠซ แˆแŠ”แ‰ณ แ‰ฐแˆแŒฅแˆฏแˆ", + "notFound": "แ‰ฆแ‰ณ แˆ›แˆตแ‹ซแ‹ แŠ แˆแ‰ฐแŒˆแŠ˜แˆ", + "cancelled": "แ‰ฆแ‰ณ แˆ›แˆตแ‹ซแ‹ แ‰ฐแˆฐแˆญแ‹Ÿแˆ", + "confirmed": "แ‰ฆแ‰ณ แˆ›แˆตแ‹ซแ‹ แ‰ฐแˆจแŒ‹แŒแŒงแˆ" + }, + "payment": { + "succeeded": "แŠญแแ‹ซ แ‰ฐแˆณแŠญแ‰ทแˆ", + "failed": "แŠญแแ‹ซ แŠ แˆแ‰ฐแˆณแŠซแˆ", + "pending": "แŠญแแ‹ซ แ‰ แˆ˜แŒ แ‰ฃแ‰ แ‰… แˆ‹แ‹ญ" + }, + "ticket": { + "issued": "แ‰ตแŠฌแ‰ต แ‰ฐแˆฐแŒฅแ‰ทแˆ", + "validated": "แ‰ตแŠฌแ‰ต แ‰ฐแˆจแŒ‹แŒแŒงแˆ", + "alreadyValidated": "แ‰ตแŠฌแ‰ต แ‰€แ‹ตแˆžแ‹แŠ‘ แ‰ฐแˆจแŒ‹แŒแŒงแˆ" + } +} diff --git a/apps/edr-passenger-api/src/common/i18n/translations/en.json b/apps/edr-passenger-api/src/common/i18n/translations/en.json new file mode 100644 index 000000000..dd1428f02 --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/translations/en.json @@ -0,0 +1,23 @@ +{ + "common": { + "welcome": "Welcome", + "error": "An error occurred", + "success": "Success" + }, + "booking": { + "created": "Booking created successfully", + "notFound": "Booking not found", + "cancelled": "Booking cancelled", + "confirmed": "Booking confirmed" + }, + "payment": { + "succeeded": "Payment successful", + "failed": "Payment failed", + "pending": "Payment pending" + }, + "ticket": { + "issued": "Ticket issued", + "validated": "Ticket validated", + "alreadyValidated": "Ticket already validated" + } +} diff --git a/apps/edr-passenger-api/src/common/i18n/translations/fr.json b/apps/edr-passenger-api/src/common/i18n/translations/fr.json new file mode 100644 index 000000000..8fdd7da89 --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/translations/fr.json @@ -0,0 +1,23 @@ +{ + "common": { + "welcome": "Bienvenue", + "error": "Une erreur s'est produite", + "success": "Succรจs" + }, + "booking": { + "created": "Rรฉservation crรฉรฉe avec succรจs", + "notFound": "Rรฉservation introuvable", + "cancelled": "Rรฉservation annulรฉe", + "confirmed": "Rรฉservation confirmรฉe" + }, + "payment": { + "succeeded": "Paiement rรฉussi", + "failed": "ร‰chec du paiement", + "pending": "Paiement en attente" + }, + "ticket": { + "issued": "Billet รฉmis", + "validated": "Billet validรฉ", + "alreadyValidated": "Billet dรฉjร  validรฉ" + } +} diff --git a/apps/edr-passenger-api/src/common/i18n/translations/om.json b/apps/edr-passenger-api/src/common/i18n/translations/om.json new file mode 100644 index 000000000..1c1b4a77b --- /dev/null +++ b/apps/edr-passenger-api/src/common/i18n/translations/om.json @@ -0,0 +1,23 @@ +{ + "common": { + "welcome": "Baga nagaan dhuftan", + "error": "Dogongora uumame", + "success": "Milkaa'ina" + }, + "booking": { + "created": "Bakka qabachuu milkaa'inaan uumame", + "notFound": "Bakka qabachuu hin argamne", + "cancelled": "Bakka qabachuu haqame", + "confirmed": "Bakka qabachuu mirkaneeffame" + }, + "payment": { + "succeeded": "Kaffaltiin milkaa'e", + "failed": "Kaffaltiin hin milkoofne", + "pending": "Kaffaltiin eegaa jira" + }, + "ticket": { + "issued": "Tiikeetiin kenname", + "validated": "Tiikeetiin mirkaneeffame", + "alreadyValidated": "Tiikeetiin duraan mirkaneeffame" + } +} diff --git a/apps/edr-passenger-api/src/common/iam-adapter.spec.ts b/apps/edr-passenger-api/src/common/iam-adapter.spec.ts new file mode 100644 index 000000000..d0c404366 --- /dev/null +++ b/apps/edr-passenger-api/src/common/iam-adapter.spec.ts @@ -0,0 +1,264 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { IamGuard } from './iam-adapter'; +import { of, throwError } from 'rxjs'; + +describe('IamGuard', () => { + let guard: IamGuard; + let httpService: HttpService; + let configService: ConfigService; + let reflector: Reflector; + + const mockConfigService = { + get: jest.fn((key: string) => { + const config: Record = { + IAM_API_URL: 'https://iam.test.com/api', + IAM_ENABLED: 'true', + IAM_API_KEY: 'test-api-key', + }; + return config[key]; + }), + }; + + const mockHttpService = { + post: jest.fn(), + }; + + const mockReflector = { + get: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + IamGuard, + { provide: ConfigService, useValue: mockConfigService }, + { provide: HttpService, useValue: mockHttpService }, + { provide: Reflector, useValue: mockReflector }, + ], + }).compile(); + + guard = module.get(IamGuard); + httpService = module.get(HttpService); + configService = module.get(ConfigService); + reflector = module.get(Reflector); + + jest.clearAllMocks(); + }); + + const createMockContext = (token?: string, roles?: string[]): ExecutionContext => { + const request = { + headers: token ? { authorization: `Bearer ${token}` } : {}, + user: undefined, + }; + + return { + switchToHttp: () => ({ + getRequest: () => request, + }), + getHandler: () => ({}), + } as ExecutionContext; + }; + + describe('canActivate', () => { + it('should allow access when IAM is disabled', async () => { + mockConfigService.get.mockReturnValueOnce('false'); // IAM_ENABLED + + const context = createMockContext(); + const result = await guard.canActivate(context); + + expect(result).toBe(true); + }); + + it('should throw UnauthorizedException when no token provided', async () => { + const context = createMockContext(); + + await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); + }); + + it('should validate token and allow access', async () => { + const mockValidationResponse = { + data: { + valid: true, + payload: { + sub: 'user-123', + email: 'admin@test.com', + roles: ['ADMIN'], + permissions: ['read', 'write'], + exp: Date.now() + 3600000, + iat: Date.now(), + }, + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + mockReflector.get.mockReturnValue(null); + + const context = createMockContext('valid-token'); + const result = await guard.canActivate(context); + + expect(result).toBe(true); + expect(mockHttpService.post).toHaveBeenCalledWith( + 'https://iam.test.com/api/v1/auth/validate', + { token: 'valid-token' }, + expect.objectContaining({ + headers: expect.objectContaining({ + 'X-API-Key': 'test-api-key', + }), + }), + ); + }); + + it('should throw UnauthorizedException for invalid token', async () => { + const mockValidationResponse = { + data: { + valid: false, + error: 'Token expired', + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + + const context = createMockContext('invalid-token'); + + await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); + }); + + it('should check required roles', async () => { + const mockValidationResponse = { + data: { + valid: true, + payload: { + sub: 'user-123', + email: 'agent@test.com', + roles: ['AGENT'], + permissions: [], + exp: Date.now() + 3600000, + iat: Date.now(), + }, + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + mockReflector.get.mockReturnValue(['ADMIN', 'SUPERVISOR']); + + const context = createMockContext('valid-token'); + + await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); + }); + + it('should allow access when user has required role', async () => { + const mockValidationResponse = { + data: { + valid: true, + payload: { + sub: 'user-123', + email: 'admin@test.com', + roles: ['ADMIN'], + permissions: [], + exp: Date.now() + 3600000, + iat: Date.now(), + }, + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + mockReflector.get.mockReturnValue(['ADMIN', 'SUPERVISOR']); + + const context = createMockContext('valid-token'); + const result = await guard.canActivate(context); + + expect(result).toBe(true); + }); + + it('should handle HTTP errors gracefully', async () => { + mockHttpService.post.mockReturnValue( + throwError(() => new Error('Network error')), + ); + + const context = createMockContext('valid-token'); + + await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); + }); + + it('should attach user to request', async () => { + const mockValidationResponse = { + data: { + valid: true, + payload: { + sub: 'user-123', + email: 'admin@test.com', + roles: ['ADMIN'], + permissions: ['read', 'write'], + organizationId: 'org-456', + exp: Date.now() + 3600000, + iat: Date.now(), + }, + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + mockReflector.get.mockReturnValue(null); + + const context = createMockContext('valid-token'); + await guard.canActivate(context); + + const request = context.switchToHttp().getRequest(); + expect(request.user).toEqual({ + userId: 'user-123', + email: 'admin@test.com', + roles: ['ADMIN'], + permissions: ['read', 'write'], + organizationId: 'org-456', + }); + }); + }); + + describe('token extraction', () => { + it('should extract token from Bearer header', async () => { + const mockValidationResponse = { + data: { + valid: true, + payload: { + sub: 'user-123', + email: 'test@test.com', + roles: [], + permissions: [], + exp: Date.now() + 3600000, + iat: Date.now(), + }, + }, + }; + + mockHttpService.post.mockReturnValue(of(mockValidationResponse)); + mockReflector.get.mockReturnValue(null); + + const context = createMockContext('my-token-123'); + await guard.canActivate(context); + + expect(mockHttpService.post).toHaveBeenCalledWith( + expect.any(String), + { token: 'my-token-123' }, + expect.any(Object), + ); + }); + + it('should reject malformed authorization header', async () => { + const request = { + headers: { authorization: 'InvalidFormat token' }, + }; + + const context = { + switchToHttp: () => ({ + getRequest: () => request, + }), + getHandler: () => ({}), + } as ExecutionContext; + + await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); + }); + }); +}); diff --git a/apps/edr-passenger-api/src/common/iam-adapter.ts b/apps/edr-passenger-api/src/common/iam-adapter.ts new file mode 100644 index 000000000..fb32d9ec6 --- /dev/null +++ b/apps/edr-passenger-api/src/common/iam-adapter.ts @@ -0,0 +1,144 @@ +import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { firstValueFrom } from 'rxjs'; + +/** + * IAM Adapter for @tria-plc corporate identity integration + * + * This adapter wraps the corporate IAM guards and provides a bridge + * between the corporate identity system and the EDR passenger API. + * + * For back-office roles (agent, supervisor, admin, staff), this guard + * validates tokens against the corporate IAM service. + * + * For passenger-facing routes, the existing JWT guard is used. + */ + +export interface IamTokenPayload { + sub: string; + email: string; + roles: string[]; + permissions: string[]; + organizationId?: string; + exp: number; + iat: number; +} + +export interface IamValidationResponse { + valid: boolean; + payload?: IamTokenPayload; + error?: string; +} + +@Injectable() +export class IamGuard implements CanActivate { + private readonly iamApiUrl: string; + private readonly iamEnabled: boolean; + + constructor( + private readonly reflector: Reflector, + private readonly config: ConfigService, + private readonly http: HttpService, + ) { + this.iamApiUrl = this.config.get('IAM_API_URL') || 'https://iam.tria-plc.com/api'; + this.iamEnabled = this.config.get('IAM_ENABLED') === 'true'; + } + + async canActivate(context: ExecutionContext): Promise { + if (!this.iamEnabled) { + // IAM disabled - allow access (for development) + return true; + } + + const request = context.switchToHttp().getRequest(); + const token = this.extractToken(request); + + if (!token) { + throw new UnauthorizedException('No authentication token provided'); + } + + const validation = await this.validateToken(token); + + if (!validation.valid || !validation.payload) { + throw new UnauthorizedException(validation.error || 'Invalid token'); + } + + // Check required roles + const requiredRoles = this.reflector.get('roles', context.getHandler()); + if (requiredRoles && requiredRoles.length > 0) { + const hasRole = requiredRoles.some((role) => validation.payload!.roles.includes(role)); + if (!hasRole) { + throw new ForbiddenException('Insufficient permissions'); + } + } + + // Attach user to request + request.user = { + userId: validation.payload.sub, + email: validation.payload.email, + roles: validation.payload.roles, + permissions: validation.payload.permissions, + organizationId: validation.payload.organizationId, + }; + + return true; + } + + private extractToken(request: any): string | null { + const authHeader = request.headers.authorization; + if (!authHeader) return null; + + const parts = authHeader.split(' '); + if (parts.length !== 2 || parts[0] !== 'Bearer') return null; + + return parts[1]; + } + + private async validateToken(token: string): Promise { + try { + const response = await firstValueFrom( + this.http.post( + `${this.iamApiUrl}/v1/auth/validate`, + { token }, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.config.get('IAM_API_KEY') || '', + }, + timeout: 5000, + }, + ), + ); + + return response.data; + } catch (err) { + return { + valid: false, + error: err instanceof Error ? err.message : 'Token validation failed', + }; + } + } +} + +/** + * Decorator to mark routes as requiring IAM authentication + */ +export const UseIamAuth = () => { + // This is a marker decorator that can be used with @UseGuards(IamGuard) + return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => { + // Marker only - actual guard is applied via @UseGuards + }; +}; + +/** + * Decorator to specify required roles for IAM-protected routes + */ +export const IamRoles = (...roles: string[]) => { + return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => { + if (descriptor) { + Reflect.defineMetadata('roles', roles, descriptor.value); + } + }; +}; diff --git a/apps/edr-passenger-api/src/common/iam.module.ts b/apps/edr-passenger-api/src/common/iam.module.ts new file mode 100644 index 000000000..7a8ec9599 --- /dev/null +++ b/apps/edr-passenger-api/src/common/iam.module.ts @@ -0,0 +1,11 @@ +import { Module, Global } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { IamGuard } from './iam-adapter'; + +@Global() +@Module({ + imports: [HttpModule.register({ timeout: 5000 })], + providers: [IamGuard], + exports: [IamGuard], +}) +export class IamModule {} diff --git a/apps/edr-passenger-api/src/common/interceptors/session-activity.interceptor.ts b/apps/edr-passenger-api/src/common/interceptors/session-activity.interceptor.ts new file mode 100644 index 000000000..9c962ba60 --- /dev/null +++ b/apps/edr-passenger-api/src/common/interceptors/session-activity.interceptor.ts @@ -0,0 +1,49 @@ +import { Injectable, NestInterceptor, ExecutionContext, CallHandler, UnauthorizedException } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { PrismaService } from '../prisma.service'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class SessionActivityInterceptor implements NestInterceptor { + private readonly inactivityMinutes: number; + + constructor( + private readonly prisma: PrismaService, + private readonly config: ConfigService, + ) { + this.inactivityMinutes = parseInt(this.config.get('SESSION_INACTIVITY_MINUTES') || '30', 10); + } + + async intercept(context: ExecutionContext, next: CallHandler): Promise> { + const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); + const user = request.user; + + if (user?.userId) { + const session = await this.prisma.session.findFirst({ + where: { userId: user.userId }, + orderBy: { lastActivityAt: 'desc' }, + }); + + if (session) { + const inactiveMinutes = (Date.now() - session.lastActivityAt.getTime()) / 60000; + + if (inactiveMinutes > this.inactivityMinutes) { + await this.prisma.session.delete({ where: { id: session.id } }); + throw new UnauthorizedException('Session expired due to inactivity'); + } + + const expiryWarningMinutes = Math.max(0, this.inactivityMinutes - inactiveMinutes); + response.setHeader('X-Session-Expiry-Warning', Math.floor(expiryWarningMinutes).toString()); + + await this.prisma.session.update({ + where: { id: session.id }, + data: { lastActivityAt: new Date() }, + }); + } + } + + return next.handle().pipe(tap(() => {})); + } +} diff --git a/apps/edr-passenger-api/src/common/prisma.module.ts b/apps/edr-passenger-api/src/common/prisma.module.ts index dd4abed26..36c2ebadf 100644 --- a/apps/edr-passenger-api/src/common/prisma.module.ts +++ b/apps/edr-passenger-api/src/common/prisma.module.ts @@ -1,6 +1,7 @@ import { Module, Global } from '@nestjs/common'; import { PrismaService } from './prisma.service'; +import { SessionActivityInterceptor } from './interceptors/session-activity.interceptor'; @Global() -@Module({ providers: [PrismaService], exports: [PrismaService] }) +@Module({ providers: [PrismaService, SessionActivityInterceptor], exports: [PrismaService, SessionActivityInterceptor] }) export class PrismaModule {} diff --git a/apps/edr-passenger-api/src/common/roles.decorator.ts b/apps/edr-passenger-api/src/common/roles.decorator.ts new file mode 100644 index 000000000..ec0c377c6 --- /dev/null +++ b/apps/edr-passenger-api/src/common/roles.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; + +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles); diff --git a/apps/edr-passenger-api/src/common/roles.guard.ts b/apps/edr-passenger-api/src/common/roles.guard.ts new file mode 100644 index 000000000..7b4b3eafc --- /dev/null +++ b/apps/edr-passenger-api/src/common/roles.guard.ts @@ -0,0 +1,19 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { UserRole } from '@prisma/client'; +import { ROLES_KEY } from './roles.decorator'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles) return true; + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => user?.role === role); + } +} diff --git a/apps/edr-passenger-api/src/config/card.config.ts b/apps/edr-passenger-api/src/config/card.config.ts new file mode 100644 index 000000000..1fd36f419 --- /dev/null +++ b/apps/edr-passenger-api/src/config/card.config.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('card', () => ({ + baseUrl: process.env.CARD_BASE_URL || '', + apiKey: process.env.CARD_API_KEY || '', + webhookSecret: process.env.CARD_WEBHOOK_SECRET || '', + webhookUrl: process.env.CARD_WEBHOOK_URL || '', + returnUrl: process.env.CARD_RETURN_URL || '', +})); diff --git a/apps/edr-passenger-api/src/config/cbe.config.ts b/apps/edr-passenger-api/src/config/cbe.config.ts new file mode 100644 index 000000000..14ecd1eae --- /dev/null +++ b/apps/edr-passenger-api/src/config/cbe.config.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('cbe', () => ({ + baseUrl: process.env.CBE_BASE_URL || '', + merchantId: process.env.CBE_MERCHANT_ID || '', + secretKey: process.env.CBE_SECRET_KEY || '', + notifyUrl: process.env.CBE_NOTIFY_URL || '', + returnUrl: process.env.CBE_RETURN_URL || '', +})); diff --git a/apps/edr-passenger-api/src/config/ebirr.config.ts b/apps/edr-passenger-api/src/config/ebirr.config.ts new file mode 100644 index 000000000..0a00bca2a --- /dev/null +++ b/apps/edr-passenger-api/src/config/ebirr.config.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('ebirr', () => ({ + baseUrl: process.env.EBIRR_BASE_URL || '', + merchantCode: process.env.EBIRR_MERCHANT_CODE || '', + secretKey: process.env.EBIRR_SECRET_KEY || '', + notifyUrl: process.env.EBIRR_NOTIFY_URL || '', + returnUrl: process.env.EBIRR_RETURN_URL || '', +})); diff --git a/apps/edr-passenger-api/src/config/telebirr.config.ts b/apps/edr-passenger-api/src/config/telebirr.config.ts new file mode 100644 index 000000000..45e3a79ff --- /dev/null +++ b/apps/edr-passenger-api/src/config/telebirr.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('telebirr', () => ({ + baseUrl: process.env.TELEBIRR_BASE_URL ?? '', + webBaseUrl: process.env.TELEBIRR_WEB_BASE_URL ?? '', + fabricAppId: process.env.TELEBIRR_FABRIC_APP_ID ?? '', + appSecret: process.env.TELEBIRR_APP_SECRET ?? '', + merchantAppId: process.env.TELEBIRR_MERCHANT_APP_ID ?? '', + merchantCode: process.env.TELEBIRR_MERCHANT_CODE ?? '', + notifyUrl: process.env.TELEBIRR_NOTIFY_URL ?? '', + returnUrl: process.env.TELEBIRR_RETURN_URL ?? '', + timeoutExpress: process.env.TELEBIRR_TIMEOUT_EXPRESS ?? '15m', + privateKey: process.env.TELEBIRR_PRIVATE_KEY ?? '', + publicKey: process.env.TELEBIRR_PUBLIC_KEY ?? '', + insecureTls: process.env.TELEBIRR_INSECURE_TLS === 'true', +})); diff --git a/apps/edr-passenger-api/src/main.ts b/apps/edr-passenger-api/src/main.ts index c0ca48097..7abf8092a 100644 --- a/apps/edr-passenger-api/src/main.ts +++ b/apps/edr-passenger-api/src/main.ts @@ -17,7 +17,10 @@ async function bootstrap() { }); app.useGlobalFilters(new HttpExceptionFilter()); - app.useGlobalInterceptors(new ResponseTransformInterceptor()); + app.useGlobalInterceptors( + new ResponseTransformInterceptor(), + app.get(SessionActivityInterceptor), + ); app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); const config = new DocumentBuilder() diff --git a/apps/edr-passenger-api/src/modules/agents/agents.controller.ts b/apps/edr-passenger-api/src/modules/agents/agents.controller.ts new file mode 100644 index 000000000..aa23fe6d0 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/agents/agents.controller.ts @@ -0,0 +1,57 @@ +import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { AgentsService } from './agents.service'; +import { CreateAgentBookingDto, OpenShiftDto, CloseShiftDto } from './agents.dto'; +import { IamGuard, IamRoles } from '../../common/iam-adapter'; +import { UserRole } from '@prisma/client'; + +@ApiTags('Agents') +@Controller('agents') +@UseGuards(IamGuard) +@ApiBearerAuth('IAM-auth') +export class AgentsController { + constructor(private service: AgentsService) {} + + @Post('bookings') + @IamRoles('AGENT', 'ADMIN') + @ApiOperation({ summary: 'Create agent booking with cash payment' }) + createBooking(@Body() dto: CreateAgentBookingDto) { + return this.service.createAgentBooking(dto); + } + + @Post('shifts/open') + @IamRoles('AGENT', 'ADMIN') + @ApiOperation({ summary: 'Open agent shift' }) + openShift(@Body() dto: OpenShiftDto) { + return this.service.openShift(dto); + } + + @Post('shifts/close') + @IamRoles('AGENT', 'ADMIN') + @ApiOperation({ summary: 'Close agent shift' }) + closeShift(@Body() dto: CloseShiftDto) { + return this.service.closeShift(dto); + } + + @Get(':agentId/commissions') + @IamRoles('AGENT', 'ADMIN') + @ApiOperation({ summary: 'Get agent commissions' }) + getCommissions( + @Param('agentId') agentId: string, + @Query('dateFrom') dateFrom?: string, + @Query('dateTo') dateTo?: string + ) { + return this.service.getCommissions( + agentId, + dateFrom ? new Date(dateFrom) : undefined, + dateTo ? new Date(dateTo) : undefined + ); + } + + @Get(':agentId/shifts') + @IamRoles('AGENT', 'ADMIN') + @ApiOperation({ summary: 'Get agent shifts' }) + getShifts(@Param('agentId') agentId: string) { + return this.service.getShifts(agentId); + } +} diff --git a/apps/edr-passenger-api/src/modules/agents/agents.dto.ts b/apps/edr-passenger-api/src/modules/agents/agents.dto.ts new file mode 100644 index 000000000..75f718d23 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/agents/agents.dto.ts @@ -0,0 +1,33 @@ +import { IsString, IsInt, IsBoolean, IsOptional, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class AgentPassengerDto { + @ApiProperty() @IsString() fullName: string; + @ApiProperty() @IsString() phone: string; + @ApiProperty() @IsString() email: string; + @ApiProperty() @IsString() seatId: string; + @ApiPropertyOptional() @IsOptional() @IsString() idDocumentType?: string; + @ApiPropertyOptional() @IsOptional() @IsString() idDocumentNumber?: string; +} + +export class CreateAgentBookingDto { + @ApiProperty() @IsString() agentId: string; + @ApiProperty({ example: 'schedule-uuid' }) @IsString() scheduleId: string; + @ApiProperty({ type: [AgentPassengerDto] }) @IsArray() @ValidateNested({ each: true }) @Type(() => AgentPassengerDto) passengers: AgentPassengerDto[]; + @ApiProperty() @IsString() paymentMethod: string; + @ApiPropertyOptional() @IsOptional() @IsInt() cashReceived?: number; + @ApiPropertyOptional() @IsOptional() @IsBoolean() paperTicket?: boolean; + @ApiPropertyOptional() @IsOptional() @IsString() serviceClass?: string; +} + +export class OpenShiftDto { + @ApiProperty() @IsString() agentId: string; + @ApiPropertyOptional() @IsOptional() @IsInt() openingBalance?: number; +} + +export class CloseShiftDto { + @ApiProperty() @IsString() shiftId: string; + @ApiProperty() @IsInt() closingBalance: number; + @ApiPropertyOptional() @IsOptional() @IsString() notes?: string; +} diff --git a/apps/edr-passenger-api/src/modules/agents/agents.module.ts b/apps/edr-passenger-api/src/modules/agents/agents.module.ts new file mode 100644 index 000000000..5c5c2bb61 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/agents/agents.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { AgentsController } from './agents.controller'; +import { AgentsService } from './agents.service'; + +@Module({ + imports: [HttpModule], + controllers: [AgentsController], + providers: [AgentsService], + exports: [AgentsService] +}) +export class AgentsModule {} diff --git a/apps/edr-passenger-api/src/modules/agents/agents.service.ts b/apps/edr-passenger-api/src/modules/agents/agents.service.ts new file mode 100644 index 000000000..12982f570 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/agents/agents.service.ts @@ -0,0 +1,132 @@ +import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { CreateAgentBookingDto, OpenShiftDto, CloseShiftDto } from './agents.dto'; +import { IdDocumentType } from '@prisma/client'; + +function generateRef(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); +} + +@Injectable() +export class AgentsService { + constructor(private prisma: PrismaService) {} + + async createAgentBooking(dto: CreateAgentBookingDto) { + const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId }, include: { user: { include: { passenger: true } } } }); + if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive'); + if (!agent.user.passenger) throw new BadRequestException('Agent must have passenger account'); + + const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } }); + if (!schedule) throw new NotFoundException('Schedule not found'); + + const seatIds = dto.passengers.map(p => p.seatId); + const seats = await this.prisma.seat.findMany({ where: { id: { in: seatIds } } }); + if (seats.length !== seatIds.length) throw new BadRequestException('Invalid seat selection'); + + const baseFare = 45000 * dto.passengers.length; + const totalMinor = baseFare; + + const booking = await this.prisma.booking.create({ + data: { + bookingRef: generateRef(), + passengerId: agent.user.passenger.id, + scheduleId: dto.scheduleId, + status: dto.paymentMethod === 'CASH' ? 'CONFIRMED' : 'PENDING_PAYMENT', + totalMinor, + seats: { + create: dto.passengers.map(p => ({ + seat: { connect: { id: p.seatId } }, + passengerName: p.fullName, + idDocumentType: p.idDocumentType as IdDocumentType | undefined, + idDocumentNumber: p.idDocumentNumber + })) + } + }, + include: { seats: true } + }); + + await this.prisma.seat.updateMany({ + where: { id: { in: seatIds } }, + data: { status: 'BOOKED' } + }); + + const changeGiven = dto.cashReceived ? dto.cashReceived - totalMinor : 0; + await this.prisma.agentBooking.create({ + data: { + agentId: dto.agentId, + bookingId: booking.id, + paymentMethod: dto.paymentMethod, + cashReceived: dto.cashReceived, + changeGiven, + paperTicket: dto.paperTicket ?? false + } + }); + + const commissionAmount = Math.floor(totalMinor * agent.commissionRate / 100); + await this.prisma.agentCommission.create({ + data: { + agentId: dto.agentId, + bookingId: booking.id, + amountMinor: commissionAmount, + rate: agent.commissionRate + } + }); + + return { booking, commission: commissionAmount }; + } + + async openShift(dto: OpenShiftDto) { + const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } }); + if (!agent) throw new NotFoundException('Agent not found'); + + const openShift = await this.prisma.agentShift.findFirst({ + where: { agentId: dto.agentId, closedAt: null } + }); + if (openShift) throw new BadRequestException('Shift already open'); + + return this.prisma.agentShift.create({ + data: { + agentId: dto.agentId, + openingBalance: dto.openingBalance ?? 0 + } + }); + } + + async closeShift(dto: CloseShiftDto) { + const shift = await this.prisma.agentShift.findUnique({ where: { id: dto.shiftId } }); + if (!shift) throw new NotFoundException('Shift not found'); + if (shift.closedAt) throw new BadRequestException('Shift already closed'); + + return this.prisma.agentShift.update({ + where: { id: dto.shiftId }, + data: { + closedAt: new Date(), + closingBalance: dto.closingBalance, + notes: dto.notes, + reconciled: true + } + }); + } + + async getCommissions(agentId: string, dateFrom?: Date, dateTo?: Date) { + return this.prisma.agentCommission.findMany({ + where: { + agentId, + createdAt: { + gte: dateFrom, + lte: dateTo + } + }, + orderBy: { createdAt: 'desc' } + }); + } + + async getShifts(agentId: string) { + return this.prisma.agentShift.findMany({ + where: { agentId }, + orderBy: { openedAt: 'desc' }, + take: 20 + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts index ea128b261..6d31c8323 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts @@ -1,7 +1,7 @@ -import { Body, Controller, Post } from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { Body, Controller, Post, HttpCode, HttpStatus } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; import { AuthService } from './auth.service'; -import { RegisterDto, LoginDto } from './auth.dto'; +import { RegisterDto, LoginDto, RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto'; @ApiTags('Auth') @Controller('auth') @@ -9,10 +9,73 @@ export class AuthController { constructor(private service: AuthService) {} @Post('register') - @ApiOperation({ summary: 'Register new user' }) + @ApiOperation({ + summary: 'Register new passenger account', + description: 'Create a new passenger account with email, phone, and password. Returns user details and JWT token for immediate login.' + }) + @ApiResponse({ status: 201, description: 'Account created successfully. Returns user object and JWT token.' }) + @ApiResponse({ status: 400, description: 'Validation error (invalid email, weak password, etc.)' }) + @ApiResponse({ status: 409, description: 'Email or phone already registered' }) + @ApiBody({ type: RegisterDto }) register(@Body() dto: RegisterDto) { return this.service.register(dto); } @Post('login') - @ApiOperation({ summary: 'Login and get JWT' }) + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Login with email and password', + description: 'Authenticate user and receive JWT token. Token expires in 7 days by default. Failed login attempts are tracked and account may be locked after 5 consecutive failures.' + }) + @ApiResponse({ status: 200, description: 'Login successful. Returns JWT token and user details.' }) + @ApiResponse({ status: 401, description: 'Invalid credentials or account locked' }) + @ApiResponse({ status: 403, description: 'Account temporarily blocked due to fraud detection' }) + @ApiBody({ type: LoginDto }) login(@Body() dto: LoginDto) { return this.service.login(dto); } + + @Post('otp/request') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Request OTP verification code', + description: 'Send a 6-digit OTP code to user email. Code expires in 10 minutes. Used for registration verification, password reset, or two-factor authentication.' + }) + @ApiResponse({ status: 200, description: 'OTP sent successfully to email' }) + @ApiResponse({ status: 404, description: 'Email not found (for PASSWORD_RESET purpose)' }) + @ApiResponse({ status: 429, description: 'Too many OTP requests. Please wait before requesting again.' }) + @ApiBody({ type: RequestOtpDto }) + requestOtp(@Body() dto: RequestOtpDto) { return this.service.requestOtp(dto); } + + @Post('otp/verify') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Verify OTP code', + description: 'Validate the 6-digit OTP code sent to user email. Code must match and not be expired.' + }) + @ApiResponse({ status: 200, description: 'OTP verified successfully' }) + @ApiResponse({ status: 400, description: 'Invalid or expired OTP code' }) + @ApiResponse({ status: 404, description: 'No OTP found for this email and purpose' }) + @ApiBody({ type: VerifyOtpDto }) + verifyOtp(@Body() dto: VerifyOtpDto) { return this.service.verifyOtp(dto); } + + @Post('password/reset-request') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Request password reset link', + description: 'Send password reset link to user email. Link contains a secure token valid for 1 hour.' + }) + @ApiResponse({ status: 200, description: 'Password reset email sent successfully' }) + @ApiResponse({ status: 404, description: 'Email not found' }) + @ApiResponse({ status: 429, description: 'Too many reset requests. Please wait before trying again.' }) + @ApiBody({ type: RequestPasswordResetDto }) + requestPasswordReset(@Body() dto: RequestPasswordResetDto) { return this.service.requestPasswordReset(dto); } + + @Post('password/reset') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Reset password with token', + description: 'Reset user password using the token received via email. Token is single-use and expires after 1 hour.' + }) + @ApiResponse({ status: 200, description: 'Password reset successfully' }) + @ApiResponse({ status: 400, description: 'Invalid, expired, or already used token' }) + @ApiResponse({ status: 404, description: 'User not found' }) + @ApiBody({ type: ResetPasswordDto }) + resetPassword(@Body() dto: ResetPasswordDto) { return this.service.resetPassword(dto); } } diff --git a/apps/edr-passenger-api/src/modules/auth/auth.dto.ts b/apps/edr-passenger-api/src/modules/auth/auth.dto.ts index 8cfe1ac65..d44159c67 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.dto.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.dto.ts @@ -1,14 +1,152 @@ -import { IsEmail, IsString, MinLength } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class RegisterDto { - @ApiProperty({ example: 'Kelemu Ketsela' }) @IsString() fullName: string; - @ApiProperty({ example: 'kelemu@email.com' }) @IsEmail() email: string; - @ApiProperty({ example: '+251912345678' }) @IsString() phone: string; - @ApiProperty({ example: 'password123', minLength: 8 }) @IsString() @MinLength(8) password: string; + @ApiProperty({ + description: 'Full name of the passenger', + example: 'Kelemu Ketsela', + minLength: 2, + maxLength: 100 + }) + @IsString() + fullName: string; + + @ApiProperty({ + description: 'Email address (must be unique)', + example: 'kelemu@email.com', + format: 'email' + }) + @IsEmail() + email: string; + + @ApiProperty({ + description: 'Phone number with country code', + example: '+251912345678', + pattern: '^\\+[1-9]\\d{1,14}$' + }) + @IsString() + phone: string; + + @ApiProperty({ + description: 'Password (minimum 8 characters)', + example: 'SecurePass123', + minLength: 8, + format: 'password' + }) + @IsString() + @MinLength(8) + password: string; + + @ApiPropertyOptional({ + description: 'Nationality of the passenger', + example: 'Ethiopian' + }) + @IsOptional() + @IsString() + nationality?: string; + + @ApiPropertyOptional({ + description: 'National ID number', + example: 'ET123456789' + }) + @IsOptional() + @IsString() + nationalId?: string; + + @ApiPropertyOptional({ + description: 'Passport number for international travelers', + example: 'P1234567' + }) + @IsOptional() + @IsString() + passportNumber?: string; } export class LoginDto { - @ApiProperty({ example: 'kelemu@email.com' }) @IsEmail() email: string; - @ApiProperty({ example: 'password123' }) @IsString() password: string; + @ApiProperty({ + description: 'Registered email address', + example: 'kelemu@email.com', + format: 'email' + }) + @IsEmail() + email: string; + + @ApiProperty({ + description: 'Account password', + example: 'password123', + format: 'password' + }) + @IsString() + password: string; +} + +export class RequestOtpDto { + @ApiProperty({ + description: 'Email address to send OTP', + example: 'kelemu@email.com' + }) + @IsEmail() + email: string; + + @ApiProperty({ + description: 'Purpose of OTP (REGISTRATION, PASSWORD_RESET, VERIFICATION)', + example: 'REGISTRATION', + enum: ['REGISTRATION', 'PASSWORD_RESET', 'VERIFICATION'] + }) + @IsString() + purpose: string; +} + +export class VerifyOtpDto { + @ApiProperty({ + description: 'Email address', + example: 'kelemu@email.com' + }) + @IsEmail() + email: string; + + @ApiProperty({ + description: '6-digit OTP code', + example: '123456', + minLength: 6, + maxLength: 6 + }) + @IsString() + code: string; + + @ApiProperty({ + description: 'Purpose of OTP verification', + example: 'REGISTRATION', + enum: ['REGISTRATION', 'PASSWORD_RESET', 'VERIFICATION'] + }) + @IsString() + purpose: string; +} + +export class RequestPasswordResetDto { + @ApiProperty({ + description: 'Email address of the account', + example: 'kelemu@email.com' + }) + @IsEmail() + email: string; +} + +export class ResetPasswordDto { + @ApiProperty({ + description: 'Password reset token received via email', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' + }) + @IsString() + token: string; + + @ApiProperty({ + description: 'New password (minimum 8 characters)', + example: 'NewSecurePass123', + minLength: 8, + format: 'password' + }) + @IsString() + @MinLength(8) + newPassword: string; } diff --git a/apps/edr-passenger-api/src/modules/auth/auth.service.ts b/apps/edr-passenger-api/src/modules/auth/auth.service.ts index 7ba9937d8..deb2e9da4 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.service.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.service.ts @@ -1,8 +1,9 @@ -import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common'; +import { Injectable, UnauthorizedException, ConflictException, BadRequestException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { PrismaService } from '../../common/prisma.service'; -import { RegisterDto, LoginDto } from './auth.dto'; +import { RegisterDto, LoginDto, RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto'; import * as bcrypt from 'bcrypt'; +import * as crypto from 'crypto'; @Injectable() export class AuthService { @@ -15,28 +16,115 @@ export class AuthService { if (exists) throw new ConflictException('Email or phone already registered'); const passwordHash = await bcrypt.hash(dto.password, 10); const user = await this.prisma.user.create({ - data: { fullName: dto.fullName, email: dto.email, phone: dto.phone, passwordHash }, + data: { + fullName: dto.fullName, + email: dto.email, + phone: dto.phone, + passwordHash, + nationality: dto.nationality, + nationalId: dto.nationalId, + passportNumber: dto.passportNumber + }, }); const passenger = await this.prisma.passenger.create({ data: { userId: user.id } }); await this.prisma.loyaltyAccount.create({ data: { passengerId: passenger.id } }); await this.prisma.walletAccount.create({ data: { passengerId: passenger.id } }); await this.prisma.userPreferences.create({ data: { userId: user.id } }); + await this.createAuditLog(user.id, 'USER_REGISTERED', 'User', user.id, null, { email: user.email }); return this.signToken(user.id, user.email, user.role, passenger.id); } async login(dto: LoginDto) { const user = await this.prisma.user.findUnique({ where: { email: dto.email }, - include: { passenger: true }, + include: { passenger: true, agent: true }, }); - if (!user || !(await bcrypt.compare(dto.password, user.passwordHash))) { + if (!user) throw new UnauthorizedException('Invalid credentials'); + + if (user.lockedUntil && user.lockedUntil > new Date()) { + throw new UnauthorizedException(`Account locked until ${user.lockedUntil.toISOString()}`); + } + + if (!(await bcrypt.compare(dto.password, user.passwordHash))) { + await this.prisma.user.update({ + where: { id: user.id }, + data: { + failedLoginAttempts: { increment: 1 }, + lockedUntil: user.failedLoginAttempts >= 4 ? new Date(Date.now() + 15 * 60 * 1000) : null + } + }); throw new UnauthorizedException('Invalid credentials'); } - return this.signToken(user.id, user.email, user.role, user.passenger?.id); + + await this.prisma.user.update({ + where: { id: user.id }, + data: { failedLoginAttempts: 0, lockedUntil: null } + }); + + await this.createAuditLog(user.id, 'USER_LOGIN', 'User', user.id, null, null); + return this.signToken(user.id, user.email, user.role, user.passenger?.id, user.agent?.id); } - private signToken(userId: string, email: string, role: string, passengerId?: string) { - const token = this.jwt.sign({ sub: userId, email, role, passengerId }); - return { token, user: { id: userId, email, role, passengerId } }; + async requestOtp(dto: RequestOtpDto) { + const code = Math.floor(100000 + Math.random() * 900000).toString(); + const expiresAt = new Date(Date.now() + 10 * 60 * 1000); + await this.prisma.otpCode.create({ + data: { email: dto.email, code, purpose: dto.purpose, expiresAt } + }); + console.log(`[OTP] ${dto.email} - ${code} (${dto.purpose})`); + return { sent: true, expiresIn: 600 }; + } + + async verifyOtp(dto: VerifyOtpDto) { + const otp = await this.prisma.otpCode.findFirst({ + where: { email: dto.email, code: dto.code, purpose: dto.purpose, verified: false, expiresAt: { gt: new Date() } }, + orderBy: { createdAt: 'desc' } + }); + if (!otp) throw new BadRequestException('Invalid or expired OTP'); + await this.prisma.otpCode.update({ where: { id: otp.id }, data: { verified: true } }); + return { verified: true }; + } + + async requestPasswordReset(dto: RequestPasswordResetDto) { + const user = await this.prisma.user.findUnique({ where: { email: dto.email } }); + if (!user) return { sent: true }; + const token = crypto.randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + 60 * 60 * 1000); + await this.prisma.passwordResetToken.create({ + data: { userId: user.id, token, expiresAt } + }); + console.log(`[PASSWORD_RESET] ${dto.email} - ${token}`); + return { sent: true }; + } + + async resetPassword(dto: ResetPasswordDto) { + const resetToken = await this.prisma.passwordResetToken.findUnique({ + where: { token: dto.token } + }); + if (!resetToken || resetToken.used || resetToken.expiresAt < new Date()) { + throw new BadRequestException('Invalid or expired reset token'); + } + const passwordHash = await bcrypt.hash(dto.newPassword, 10); + await this.prisma.user.update({ + where: { id: resetToken.userId }, + data: { passwordHash, failedLoginAttempts: 0, lockedUntil: null } + }); + await this.prisma.passwordResetToken.update({ + where: { id: resetToken.id }, + data: { used: true } + }); + await this.createAuditLog(resetToken.userId, 'PASSWORD_RESET', 'User', resetToken.userId, null, null); + return { reset: true }; + } + + private signToken(userId: string, email: string, role: string, passengerId?: string, agentId?: string) { + const token = this.jwt.sign({ sub: userId, email, role, passengerId, agentId }); + return { token, user: { id: userId, email, role, passengerId, agentId } }; + } + + private async createAuditLog(userId: string, action: string, entityType: string, entityId: string, oldData: any, newData: any) { + await this.prisma.auditLog.create({ + data: { userId, action, entityType, entityId, oldData, newData } + }); } } diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts index eb7e533b5..a0fb8afba 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts @@ -1,7 +1,7 @@ -import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, Param, Post, Patch, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; import { BookingsService } from './bookings.service'; -import { CreateBookingDto } from './bookings.dto'; +import { CreateBookingDto, ModifyBookingDto, CancelBookingDto } from './bookings.dto'; import { JwtGuard } from '../../common/jwt.guard'; @ApiTags('Booking') @@ -10,7 +10,54 @@ import { JwtGuard } from '../../common/jwt.guard'; @ApiBearerAuth('JWT-auth') export class BookingsController { constructor(private service: BookingsService) {} - @Post() @ApiOperation({ summary: 'Create booking from seat hold' }) create(@Body() dto: CreateBookingDto) { return this.service.create(dto); } - @Get(':bookingRef') @ApiOperation({ summary: 'Get booking by reference' }) getByRef(@Param('bookingRef') ref: string) { return this.service.getByRef(ref); } - @Delete(':bookingRef')@ApiOperation({ summary: 'Cancel booking' }) cancel(@Param('bookingRef') ref: string) { return this.service.cancel(ref); } + + @Post() + @ApiOperation({ + summary: 'Create booking with age-based pricing and Verifayda verification', + description: `Creates a booking with the following features: + - Age-based pricing: CHILD (<5 years) first child free, ADULT (>=5 years) full fare + - Ethiopian nationals: Verified via Verifayda 2.0 (national ID NOT stored) + - Non-Ethiopians: Passport required, no verification + - Multi-currency: Display in ETB, DJF, or USD (transaction always in ETB) + - All passengers require dateOfBirth for age calculation` + }) + @ApiResponse({ status: 201, description: 'Booking created with fare breakdown' }) + @ApiResponse({ status: 400, description: 'Verifayda verification failed or invalid passenger data' }) + @ApiResponse({ status: 404, description: 'Trip or seat hold not found' }) + create(@Body() dto: CreateBookingDto) { + return this.service.create(dto); + } + + @Get(':bookingRef') + @ApiOperation({ + summary: 'Get booking details by reference', + description: 'Returns booking with passenger categories, Verifayda verification status, and multi-currency amounts' + }) + @ApiResponse({ status: 200, description: 'Booking details with adult/child counts and currency conversion' }) + @ApiResponse({ status: 404, description: 'Booking not found' }) + getByRef(@Param('bookingRef') ref: string) { + return this.service.getByRef(ref); + } + + @Patch(':bookingRef/modify') + @ApiOperation({ + summary: 'Modify booking seats or trip', + description: 'Allows modification of confirmed bookings before departure' + }) + @ApiResponse({ status: 200, description: 'Booking modified successfully' }) + @ApiResponse({ status: 400, description: 'Cannot modify cancelled or past bookings' }) + modify(@Body() dto: ModifyBookingDto) { + return this.service.modify(dto); + } + + @Delete(':bookingRef') + @ApiOperation({ + summary: 'Cancel booking with refund', + description: 'Cancels booking and processes refund (80% for confirmed bookings)' + }) + @ApiResponse({ status: 200, description: 'Booking cancelled with refund amount' }) + @ApiResponse({ status: 400, description: 'Booking already cancelled' }) + cancel(@Param('bookingRef') ref: string, @Body() dto: CancelBookingDto) { + return this.service.cancel(ref, dto.reason); + } } diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts index a2e208d2c..82d75f2dc 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts @@ -1,22 +1,41 @@ -import { IsString, IsArray, ValidateNested, IsOptional, IsInt, IsEnum } from 'class-validator'; +import { IsString, IsArray, ValidateNested, IsOptional, IsInt, IsEnum, IsDateString } from 'class-validator'; import { Type } from 'class-transformer'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Currency, IdDocumentType } from '@prisma/client'; export class PassengerInputDto { - @ApiProperty() @IsString() fullName: string; - @ApiProperty() @IsString() phone: string; - @ApiProperty() @IsString() email: string; @ApiProperty() @IsString() seatId: string; - @ApiPropertyOptional() @IsOptional() @IsString() idDocumentType?: string; - @ApiPropertyOptional() @IsOptional() @IsString() idDocumentNumber?: string; + @ApiProperty({ example: 'John Doe' }) @IsString() passengerName: string; + @ApiProperty({ example: '1990-05-15', description: 'Date of birth for age calculation' }) @IsDateString() dateOfBirth: string; + @ApiProperty({ example: 'NATIONAL_ID', enum: IdDocumentType }) @IsEnum(IdDocumentType) idDocumentType: IdDocumentType; + @ApiPropertyOptional({ example: 'ET123456789', description: 'For Ethiopian nationals only - used for Verifayda verification' }) @IsOptional() @IsString() idDocumentNumber?: string; + @ApiPropertyOptional({ example: 'P1234567', description: 'For non-Ethiopians' }) @IsOptional() @IsString() passportNumber?: string; + @ApiPropertyOptional({ example: 'Kenya', description: 'For non-Ethiopians' }) @IsOptional() @IsString() passportCountry?: string; } export class CreateBookingDto { @ApiProperty() @IsString() passengerId: string; - @ApiProperty() @IsString() tripId: string; + @ApiProperty() @IsString() scheduleId: string; @ApiProperty() @IsString() holdId: string; + @ApiProperty({ example: 'station-uuid', description: 'Origin station UUID for this leg (must match the hold)' }) @IsString() originStationId: string; + @ApiProperty({ example: 'station-uuid', description: 'Destination station UUID for this leg (must match the hold)' }) @IsString() destinationStationId: string; @ApiProperty({ type: [PassengerInputDto] }) @IsArray() @ValidateNested({ each: true }) @Type(() => PassengerInputDto) passengers: PassengerInputDto[]; - @ApiPropertyOptional({ example: 'ECONOMY', enum: ['ECONOMY', 'BUSINESS', 'FIRST'] }) @IsOptional() @IsString() serviceClass?: string; + @ApiProperty({ example: 'seat-class-uuid', description: 'Seat class UUID' }) + @IsString() seatClassId: string; @ApiPropertyOptional() @IsOptional() @IsString() promoCode?: string; @ApiPropertyOptional() @IsOptional() @IsInt() loyaltyRedemptionPoints?: number; + @ApiPropertyOptional({ example: 'ONE_WAY' }) @IsOptional() @IsString() bookingType?: string; + @ApiPropertyOptional({ example: 'ETB', enum: Currency }) @IsOptional() @IsEnum(Currency) displayCurrency?: Currency; +} + +export class ModifyBookingDto { + @ApiProperty() @IsString() bookingRef: string; + @ApiProperty({ example: 'schedule-uuid' }) @IsString() newScheduleId: string; + @ApiProperty({ type: [String] }) @IsArray() newSeatIds: string[]; + @ApiPropertyOptional() @IsOptional() @IsString() reason?: string; +} + +export class CancelBookingDto { + @ApiProperty() @IsString() bookingRef: string; + @ApiPropertyOptional() @IsOptional() @IsString() reason?: string; } diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.module.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.module.ts index 5632af5d7..a73d16796 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.module.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.module.ts @@ -2,7 +2,13 @@ import { Module } from '@nestjs/common'; import { BookingsController } from './bookings.controller'; import { BookingsService } from './bookings.service'; import { SeatsModule } from '../seats/seats.module'; -import { SearchModule } from '../search/search.module'; +import { VerifaydaModule } from '../verifayda/verifayda.module'; +import { CurrencyModule } from '../currency/currency.module'; -@Module({ imports: [SeatsModule, SearchModule], controllers: [BookingsController], providers: [BookingsService], exports: [BookingsService] }) +@Module({ + imports: [SeatsModule, VerifaydaModule, CurrencyModule], + controllers: [BookingsController], + providers: [BookingsService], + exports: [BookingsService] +}) export class BookingsModule {} diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts index cb6158639..6645f6f63 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts @@ -2,69 +2,196 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm import { PrismaService } from '../../common/prisma.service'; import { SeatsService } from '../seats/seats.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { CreateBookingDto } from './bookings.dto'; +import { CreateBookingDto, ModifyBookingDto } from './bookings.dto'; import { Cron, CronExpression } from '@nestjs/schedule'; -import { SearchService } from '../search/search.service'; +import { VerifaydaService } from '../verifayda/verifayda.service'; +import { CurrencyService } from '../currency/currency.service'; +import { Currency, PassengerCategory, IdDocumentType } from '@prisma/client'; function generateRef(): string { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); } +function calculateAge(dateOfBirth: Date): number { + const today = new Date(); + let age = today.getFullYear() - dateOfBirth.getFullYear(); + const monthDiff = today.getMonth() - dateOfBirth.getMonth(); + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dateOfBirth.getDate())) age--; + return age; +} + @Injectable() export class BookingsService { - constructor(private prisma: PrismaService, private seatsService: SeatsService, private eventEmitter: EventEmitter2, private searchService: SearchService) {} + constructor( + private prisma: PrismaService, + private seatsService: SeatsService, + private eventEmitter: EventEmitter2, + private verifaydaService: VerifaydaService, + private currencyService: CurrencyService, + ) {} async create(dto: CreateBookingDto) { const hold = await this.prisma.seatHold.findUnique({ where: { id: dto.holdId } }); if (!hold || hold.expiresAt < new Date()) throw new BadRequestException('Seat hold expired'); - const trip = await this.prisma.trip.findUnique({ where: { id: dto.tripId }, include: { originStation: true, destinationStation: true } }); - if (!trip) throw new NotFoundException('Trip not found'); - const fareQuote = await this.searchService.getFareQuote({ tripId: dto.tripId, serviceClass: dto.serviceClass ?? 'ECONOMY', passengerCount: dto.passengers.length, promoCode: dto.promoCode, loyaltyRedemptionPoints: dto.loyaltyRedemptionPoints }); + const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true } }); + if (!schedule) throw new NotFoundException('Schedule not found'); + + const seatIds = dto.passengers.map((p) => p.seatId); + const passengersData = []; + let adultCount = 0, childCount = 0; + + for (const passenger of dto.passengers) { + const dateOfBirth = new Date(passenger.dateOfBirth); + const age = calculateAge(dateOfBirth); + const category: PassengerCategory = age < 5 ? PassengerCategory.CHILD : PassengerCategory.ADULT; + if (category === PassengerCategory.ADULT) adultCount++; else childCount++; + + let passengerName = passenger.passengerName; + let verifaydaVerified = false; + let verifaydaData: Record | undefined; + + if (passenger.idDocumentType === IdDocumentType.NATIONAL_ID && passenger.idDocumentNumber) { + const verification = await this.verifaydaService.verifyNationalId(passenger.idDocumentNumber); + if (!verification.verified) throw new BadRequestException(`Verifayda verification failed for ${passenger.passengerName}: ${verification.failureReason}`); + passengerName = verification.passengerData?.fullName || passengerName; + verifaydaVerified = true; + verifaydaData = verification.passengerData?.profileData; + } else if (passenger.idDocumentType === IdDocumentType.PASSPORT) { + if (!passenger.passportNumber || !passenger.passportCountry) throw new BadRequestException(`Passport number and country required for ${passenger.passengerName}`); + } + + passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData }); + } + + const baseFareMinor = await this.getBaseFare(dto.scheduleId, dto.seatClassId); + const adultFareMinor = baseFareMinor * adultCount; + const paidChildrenCount = Math.max(0, childCount - 1); + const childFareMinor = baseFareMinor * paidChildrenCount; + const totalBaseFareMinor = adultFareMinor + childFareMinor; + + let discountMinor = 0; + if (dto.promoCode) { + const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } }); + if (promo?.active && promo.validUntil > new Date()) { + discountMinor = promo.percentOff ? Math.round(totalBaseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0); + } + } + + const loyaltyMinor = (dto.loyaltyRedemptionPoints ?? 0) * 10; + const taxesMinor = Math.round(totalBaseFareMinor * 0.05); + const totalMinor = Math.max(0, totalBaseFareMinor - discountMinor - loyaltyMinor + taxesMinor); + + const displayCurrency = dto.displayCurrency || Currency.ETB; + let displayTotalMinor = totalMinor; + if (displayCurrency !== Currency.ETB) { + displayTotalMinor = await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency); + } + const booking = await this.prisma.booking.create({ - data: { bookingRef: generateRef(), passengerId: dto.passengerId, tripId: dto.tripId, status: 'PENDING_PAYMENT', totalMinor: fareQuote.totalMinor, seats: { create: dto.passengers.map((p) => ({ seatId: p.seatId, passengerName: p.fullName, idDocumentType: p.idDocumentType, idDocumentNumber: p.idDocumentNumber })) } }, - include: { seats: { include: { seat: true } }, trip: { include: { originStation: true, destinationStation: true, service: true } } }, + data: { + bookingRef: generateRef(), + passengerId: dto.passengerId, + scheduleId: dto.scheduleId, + status: 'PENDING_PAYMENT', + totalMinor, adultCount, childCount, displayCurrency, displayTotalMinor, + bookingType: dto.bookingType ?? 'ONE_WAY', + seats: { + create: passengersData.map((p) => ({ + seat: { connect: { id: p.seatId } }, + passengerName: p.passengerName, + dateOfBirth: p.dateOfBirth, + passengerCategory: p.category, + idDocumentType: p.idDocumentType, + idDocumentNumber: p.idDocumentType === IdDocumentType.NATIONAL_ID ? undefined : p.idDocumentNumber, + passportNumber: p.passportNumber, + passportCountry: p.passportCountry, + verifaydaVerified: p.verifaydaVerified, + verifaydaData: p.verifaydaData || undefined, + fareMinor: p.category === PassengerCategory.ADULT ? baseFareMinor : (paidChildrenCount > 0 ? baseFareMinor : 0), + displayCurrency, + })), + }, + }, + include: { seats: { include: { seat: true } }, schedule: { include: { originStation: true, destinationStation: true, train: true } } }, }); + + await this.seatsService.confirmSeats(seatIds); this.eventEmitter.emit('booking.created', { booking }); - return booking; + + return { + ...booking, + fareBreakdown: { baseFareMinor, adultCount, adultFareMinor, childCount, freeChildrenCount: Math.min(childCount, 1), paidChildrenCount, childFareMinor, totalBaseFareMinor, discountMinor, loyaltyRedemptionMinor: loyaltyMinor, taxesFeesMinor: taxesMinor, totalMinor, currency: 'ETB', displayCurrency, displayTotalMinor }, + }; + } + + private async getBaseFare(scheduleId: string, seatClassId: string): Promise { + const fareRule = await this.prisma.fareRule.findFirst({ where: { tripId: scheduleId, seatClassId } }); + return fareRule?.baseFareMinor ?? 35000; } async getByRef(bookingRef: string) { - const booking = await this.prisma.booking.findUnique({ where: { bookingRef }, include: { trip: { include: { originStation: true, destinationStation: true, service: true } }, seats: { include: { seat: { include: { coach: true } } } }, paymentIntent: true, ticket: true } }); + const booking = await this.prisma.booking.findUnique({ + where: { bookingRef }, + include: { + schedule: { include: { originStation: true, destinationStation: true, train: true } }, + seats: { include: { seat: { include: { coach: { include: { seatClass: true } } } } } }, + paymentIntent: true, ticket: true, + }, + }); if (!booking) throw new NotFoundException('Booking not found'); return { - id: booking.id, - bookingRef: booking.bookingRef, - status: booking.status, - totalFare: booking.totalMinor / 100, - createdAt: booking.createdAt, - trip: { - number: booking.trip.service.number, - origin: { id: booking.trip.originStation.id, name: booking.trip.originStation.name, code: booking.trip.originStation.code, city: booking.trip.originStation.city }, - destination: { id: booking.trip.destinationStation.id, name: booking.trip.destinationStation.name, code: booking.trip.destinationStation.code, city: booking.trip.destinationStation.city }, - departureAt: booking.trip.departureAt, - arrivalAt: booking.trip.arrivalAt, + id: booking.id, bookingRef: booking.bookingRef, status: booking.status, + totalFare: booking.totalMinor / 100, adultCount: booking.adultCount, childCount: booking.childCount, + displayCurrency: booking.displayCurrency, displayTotalFare: booking.displayTotalMinor ? booking.displayTotalMinor / 100 : undefined, + bookingType: booking.bookingType, createdAt: booking.createdAt, + schedule: { + number: booking.schedule.train.number, + origin: { id: booking.schedule.originStation.id, name: booking.schedule.originStation.name, code: booking.schedule.originStation.code, city: booking.schedule.originStation.city }, + destination: { id: booking.schedule.destinationStation.id, name: booking.schedule.destinationStation.name, code: booking.schedule.destinationStation.code, city: booking.schedule.destinationStation.city }, + departureAt: booking.schedule.departureAt, arrivalAt: booking.schedule.arrivalAt, }, passengers: booking.seats.map((bs) => ({ - fullName: bs.passengerName, - seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.serviceClass }, + fullName: bs.passengerName, category: bs.passengerCategory, verifaydaVerified: bs.verifaydaVerified, + seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.seatClass.name }, })), payment: booking.paymentIntent ? { method: booking.paymentIntent.method, status: booking.paymentIntent.status } : undefined, }; } - async cancel(bookingRef: string) { - const booking = await this.prisma.booking.findUnique({ where: { bookingRef }, include: { seats: true } }); + async modify(dto: ModifyBookingDto) { + const booking = await this.prisma.booking.findUnique({ where: { bookingRef: dto.bookingRef }, include: { seats: true, schedule: true } }); if (!booking) throw new NotFoundException('Booking not found'); - if (booking.status === 'CONFIRMED') throw new BadRequestException('Use refund for confirmed bookings'); + if (booking.status !== 'CONFIRMED') throw new BadRequestException('Only confirmed bookings can be modified'); + if (booking.schedule.departureAt < new Date()) throw new BadRequestException('Cannot modify past bookings'); + + const oldSeats = booking.seats.map(s => s.seatId); + await this.prisma.bookingModification.create({ + data: { bookingId: booking.id, modifiedBy: booking.passengerId, modificationType: 'SEAT_CHANGE', oldData: { scheduleId: booking.scheduleId, seatIds: oldSeats }, newData: { scheduleId: dto.newScheduleId, seatIds: dto.newSeatIds }, fareAdjustment: 0, reason: dto.reason }, + }); + await this.seatsService.releaseSeats(oldSeats); + await this.seatsService.confirmSeats(dto.newSeatIds); + return { modified: true, bookingRef: dto.bookingRef }; + } + + async cancel(bookingRef: string, reason?: string) { + const booking = await this.prisma.booking.findUnique({ where: { bookingRef }, include: { seats: true, paymentIntent: true } }); + if (!booking) throw new NotFoundException('Booking not found'); + if (booking.status === 'CANCELLED') throw new BadRequestException('Booking already cancelled'); + const refundAmount = booking.status === 'CONFIRMED' ? Math.floor(booking.totalMinor * 0.8) : 0; + await this.prisma.bookingCancellation.create({ data: { bookingId: booking.id, cancelledBy: booking.passengerId, reason, refundAmount, refundMethod: booking.paymentIntent?.method ?? 'ORIGINAL', refundStatus: 'PENDING' } }); await this.seatsService.releaseSeats(booking.seats.map((s) => s.seatId)); - return this.prisma.booking.update({ where: { bookingRef }, data: { status: 'CANCELLED' } }); + await this.prisma.booking.update({ where: { bookingRef }, data: { status: 'CANCELLED' } }); + return { cancelled: true, refundAmount: refundAmount / 100, currency: 'ETB' }; } @Cron(CronExpression.EVERY_MINUTE) async expirePendingBookings() { const cutoff = new Date(Date.now() - 20 * 60 * 1000); const expired = await this.prisma.booking.findMany({ where: { status: 'PENDING_PAYMENT', createdAt: { lt: cutoff } }, include: { seats: true } }); - for (const b of expired) { await this.seatsService.releaseSeats(b.seats.map((s) => s.seatId)); await this.prisma.booking.update({ where: { id: b.id }, data: { status: 'CANCELLED' } }); } + for (const b of expired) { + await this.seatsService.releaseSeats(b.seats.map((s) => s.seatId)); + await this.prisma.booking.update({ where: { id: b.id }, data: { status: 'CANCELLED' } }); + } } } diff --git a/apps/edr-passenger-api/src/modules/currency/currency.module.ts b/apps/edr-passenger-api/src/modules/currency/currency.module.ts new file mode 100644 index 000000000..445f31e05 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/currency/currency.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { CurrencyService } from './currency.service'; +import { PrismaModule } from '../../common/prisma.module'; + +@Module({ + imports: [PrismaModule], + providers: [CurrencyService], + exports: [CurrencyService], +}) +export class CurrencyModule {} diff --git a/apps/edr-passenger-api/src/modules/currency/currency.service.ts b/apps/edr-passenger-api/src/modules/currency/currency.service.ts new file mode 100644 index 000000000..3fcfce179 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/currency/currency.service.ts @@ -0,0 +1,83 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { Currency } from '@prisma/client'; + +@Injectable() +export class CurrencyService { + private readonly logger = new Logger(CurrencyService.name); + + constructor(private readonly prisma: PrismaService) {} + + async convertAmount( + amountMinor: number, + fromCurrency: Currency, + toCurrency: Currency, + ): Promise { + if (fromCurrency === toCurrency) { + return amountMinor; + } + + const rate = await this.getExchangeRate(fromCurrency, toCurrency); + return Math.round(amountMinor * rate); + } + + async getExchangeRate( + fromCurrency: Currency, + toCurrency: Currency, + ): Promise { + const exchangeRate = await this.prisma.currencyExchangeRate.findFirst({ + where: { + fromCurrency, + toCurrency, + }, + orderBy: { + effectiveDate: 'desc', + }, + }); + + if (!exchangeRate) { + this.logger.warn( + `No exchange rate found for ${fromCurrency} to ${toCurrency}, using 1.0`, + ); + return 1.0; + } + + return Number(exchangeRate.rate); + } + + async syncExchangeRates(): Promise { + this.logger.log('Syncing exchange rates from external provider'); + + // In production, fetch from external API + // For now, using static rates + const rates = [ + { from: 'ETB', to: 'ETB', rate: 1.0 }, + { from: 'ETB', to: 'DJF', rate: 3.25 }, + { from: 'ETB', to: 'USD', rate: 0.018 }, + { from: 'DJF', to: 'ETB', rate: 0.3077 }, + { from: 'USD', to: 'ETB', rate: 55.56 }, + ]; + + for (const { from, to, rate } of rates) { + await this.prisma.currencyExchangeRate.upsert({ + where: { + fromCurrency_toCurrency_effectiveDate: { + fromCurrency: from as Currency, + toCurrency: to as Currency, + effectiveDate: new Date(), + }, + }, + update: { rate }, + create: { + fromCurrency: from as Currency, + toCurrency: to as Currency, + rate, + effectiveDate: new Date(), + source: 'EXTERNAL_API', + }, + }); + } + + this.logger.log('Exchange rates synced successfully'); + } +} diff --git a/apps/edr-passenger-api/src/modules/dashboard/dashboard.service.ts b/apps/edr-passenger-api/src/modules/dashboard/dashboard.service.ts index 22b6ddeb1..e104a507f 100644 --- a/apps/edr-passenger-api/src/modules/dashboard/dashboard.service.ts +++ b/apps/edr-passenger-api/src/modules/dashboard/dashboard.service.ts @@ -10,8 +10,12 @@ export class DashboardService { const [passenger, upcomingBooking, wallet, promos, weatherAlerts, stationSignals, savedRoutes] = await Promise.all([ this.prisma.passenger.findUnique({ where: { id: passengerId }, include: { user: { select: { fullName: true } }, loyalty: true } }), this.prisma.booking.findFirst({ - where: { passengerId, status: 'CONFIRMED', trip: { departureAt: { gte: now } } }, - include: { trip: { include: { originStation: true, destinationStation: true, service: true, liveStatus: true } }, seats: { include: { seat: { include: { coach: true } } }, take: 1 }, ticket: true }, + where: { passengerId, status: 'CONFIRMED', schedule: { departureAt: { gte: now } } }, + include: { + schedule: { include: { originStation: true, destinationStation: true, train: true, liveStatus: true } }, + seats: { include: { seat: { include: { coach: true } } }, take: 1 }, + ticket: true, + }, orderBy: { createdAt: 'asc' }, }), this.prisma.walletAccount.findUnique({ where: { passengerId } }), @@ -30,10 +34,10 @@ export class DashboardService { user: { firstName, greetingKey }, upcomingTicket: upcomingBooking ? { ticketId: upcomingBooking.ticket?.id, bookingRef: upcomingBooking.bookingRef, - from: upcomingBooking.trip.originStation.name, to: upcomingBooking.trip.destinationStation.name, - trainName: upcomingBooking.trip.service.name, coachLabel: seat?.seat.coach.label, seatLabel: seat?.seat.label, - departureAt: upcomingBooking.trip.departureAt, - punctualityLabel: (upcomingBooking.trip.liveStatus?.delayMinutes ?? 0) > 0 ? 'DELAYED' : 'ON_TIME', + from: upcomingBooking.schedule.originStation.name, to: upcomingBooking.schedule.destinationStation.name, + trainName: upcomingBooking.schedule.train.name, coachLabel: seat?.seat.coach.label, seatLabel: seat?.seat.label, + departureAt: upcomingBooking.schedule.departureAt, + punctualityLabel: (upcomingBooking.schedule.liveStatus?.delayMinutes ?? 0) > 0 ? 'DELAYED' : 'ON_TIME', } : null, wallet: wallet ? { balanceMinor: wallet.balanceMinor, currency: wallet.currency } : null, activePromotionsCount: promos, diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.controller.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.controller.ts index 1557c2ece..bc236a8f8 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.controller.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.controller.ts @@ -1,7 +1,7 @@ -import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiBody, ApiResponse } from '@nestjs/swagger'; import { FleetService } from './fleet.service'; -import { CreateTrainServiceDto, CreateCoachDto, CreateSeatBatchDto } from './fleet.dto'; +import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, CreateSeatBatchDto, ListCoachesDto } from './fleet.dto'; import { JwtGuard } from '../../common/jwt.guard'; @ApiTags('Fleet') @@ -10,9 +10,92 @@ import { JwtGuard } from '../../common/jwt.guard'; @ApiBearerAuth('JWT-auth') export class FleetController { constructor(private service: FleetService) {} - @Get('services') @ApiOperation({ summary: 'List train services' }) getServices() { return this.service.getServices(); } - @Post('services') @ApiOperation({ summary: 'Create train service' }) createService(@Body() dto: CreateTrainServiceDto) { return this.service.createService(dto); } - @Post('coaches') @ApiOperation({ summary: 'Add coach to trip' }) createCoach(@Body() dto: CreateCoachDto) { return this.service.createCoach(dto); } - @Post('seats/batch')@ApiOperation({ summary: 'Batch-create seats for coach' }) createSeatBatch(@Body() dto: CreateSeatBatchDto) { return this.service.createSeatBatch(dto); } - @Get('analytics') @ApiOperation({ summary: 'Fleet analytics' }) getAnalytics() { return this.service.getAnalytics(); } + + @Get('trains') + @ApiOperation({ summary: 'List all trains with their recent schedules' }) + @ApiResponse({ status: 200, description: 'Array of trains each with up to 5 most recent schedules' }) + getTrains() { return this.service.getTrains(); } + + @Post('trains') + @ApiOperation({ summary: 'Create a train service' }) + @ApiBody({ type: CreateTrainDto }) + @ApiResponse({ status: 201, description: 'Train created' }) + createTrain(@Body() dto: CreateTrainDto) { return this.service.createTrain(dto); } + + @Get('coaches') + @ApiOperation({ summary: 'List coaches filtered by status, mode, seat class, or schedule assignment' }) + @ApiQuery({ name: 'isActive', required: false, type: Boolean, description: 'true = active only, false = inactive only, omit = all' }) + @ApiQuery({ name: 'mode', required: false, description: 'Filter by mode: seat | bed | convertible' }) + @ApiQuery({ name: 'seatClassId', required: false, description: 'Filter by SeatClass UUID' }) + @ApiQuery({ name: 'scheduleId', required: false, description: 'Filter to coaches assigned to this TrainSchedule UUID' }) + @ApiResponse({ status: 200, description: 'Coaches with seat class info, assignment count, and seat status summary (total/available/held/booked/blocked)' }) + listCoaches( + @Query('isActive') isActive?: string, + @Query('mode') mode?: string, + @Query('seatClassId') seatClassId?: string, + @Query('scheduleId') scheduleId?: string, + ) { + const dto: ListCoachesDto = { + isActive: isActive === 'true' ? true : isActive === 'false' ? false : undefined, + mode, + seatClassId, + scheduleId, + }; + return this.service.listCoaches(dto); + } + + @Get('coaches/:id') + @ApiOperation({ summary: 'Get a single coach with full seat layout and arrangement' }) + @ApiParam({ name: 'id', description: 'Coach UUID' }) + @ApiResponse({ + status: 200, + description: `Coach detail including: +- seatClass: seat class info +- seatsByRow: seats grouped by row number, each seat includes label, seatNumber, col, kind (STANDARD/PREMIUM/ACCESSIBLE), status (AVAILABLE/HELD/BOOKED/BLOCKED), isWindow, isAisle, bedPosition (bed mode only), premiumFeeMinor +- seatStatusSummary: total/available/held/booked/blocked counts +- assignments: up to 5 most recent schedule assignments with origin/destination`, + }) + @ApiResponse({ status: 404, description: 'Coach not found' }) + getCoach(@Param('id') id: string) { return this.service.getCoach(id); } + + @Post('coaches') + @ApiOperation({ summary: 'Register a new physical coach and auto-generate its seats from arrangement config' }) + @ApiBody({ type: CreateCoachDto }) + @ApiResponse({ status: 201, description: 'Coach created with seats auto-generated from mode + arrangement + totalUnits' }) + @ApiResponse({ status: 400, description: 'Invalid arrangement format' }) + createCoach(@Body() dto: CreateCoachDto) { return this.service.createCoach(dto); } + + @Patch('coaches/:id') + @ApiOperation({ summary: 'Update coach properties (label, mode, arrangement, etc.)' }) + @ApiParam({ name: 'id', description: 'Coach UUID' }) + @ApiBody({ type: UpdateCoachDto }) + @ApiResponse({ status: 200, description: 'Coach updated' }) + @ApiResponse({ status: 404, description: 'Coach not found' }) + updateCoach(@Param('id') id: string, @Body() dto: UpdateCoachDto) { return this.service.updateCoach(id, dto); } + + @Post('assignments') + @ApiOperation({ summary: 'Assign a physical coach to a train schedule at a given position' }) + @ApiBody({ type: AssignCoachDto }) + @ApiResponse({ status: 201, description: 'CoachAssignment created' }) + @ApiResponse({ status: 404, description: 'Schedule or coach not found' }) + assignCoach(@Body() dto: AssignCoachDto) { return this.service.assignCoach(dto); } + + @Delete('assignments/:id') + @ApiOperation({ summary: 'Remove a coach assignment from a schedule' }) + @ApiParam({ name: 'id', description: 'CoachAssignment UUID' }) + @ApiResponse({ status: 200, description: 'Assignment removed' }) + @ApiResponse({ status: 404, description: 'Assignment not found' }) + removeAssignment(@Param('id') id: string) { return this.service.removeAssignment(id); } + + @Post('seats/batch') + @ApiOperation({ summary: 'Batch-generate seats for a coach (rows ร— cols)' }) + @ApiBody({ type: CreateSeatBatchDto }) + @ApiResponse({ status: 201, description: 'Returns count of seats created' }) + @ApiResponse({ status: 404, description: 'Coach not found' }) + createSeatBatch(@Body() dto: CreateSeatBatchDto) { return this.service.createSeatBatch(dto); } + + @Get('analytics') + @ApiOperation({ summary: 'Fleet analytics: train count, schedule count, seat occupancy rate' }) + @ApiResponse({ status: 200, description: 'Returns totalTrains, totalSchedules, totalSeats, bookedSeats, occupancyRate' }) + getAnalytics() { return this.service.getAnalytics(); } } diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts index bef3b5a9a..6046b1068 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts @@ -1,20 +1,50 @@ -import { IsString, IsEnum, IsInt } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { ServiceClass } from '@prisma/client'; +import { IsString, IsInt, IsOptional, IsArray, IsBoolean } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional, PartialType, OmitType } from '@nestjs/swagger'; -export class CreateTrainServiceDto { - @ApiProperty({ example: '301' }) @IsString() number: string; +export class CreateTrainDto { + @ApiProperty({ example: '301', description: 'Unique train service number' }) @IsString() number: string; @ApiProperty({ example: 'Express 301' }) @IsString() name: string; + @ApiPropertyOptional({ example: 'EDR', description: 'Operator ID (defaults to op_edr)' }) @IsOptional() @IsString() operatorId?: string; + @ApiPropertyOptional({ example: 'Ethiopian-Djibouti Railway' }) @IsOptional() @IsString() operatorName?: string; + @ApiPropertyOptional({ example: 'Addis-Djibouti Express' }) @IsOptional() @IsString() description?: string; } export class CreateCoachDto { - @ApiProperty() @IsString() tripId: string; - @ApiProperty({ example: 'A' }) @IsString() label: string; - @ApiProperty({ enum: ServiceClass }) @IsEnum(ServiceClass) serviceClass: ServiceClass; + @ApiProperty({ example: 'C-A1', description: 'Unique physical coach identifier' }) @IsString() coachNumber: string; + @ApiProperty({ example: 'A', description: 'Display label shown on tickets' }) @IsString() label: string; + @ApiProperty({ example: 'seat-class-uuid', description: 'SeatClass UUID this coach belongs to' }) @IsString() seatClassId: string; + @ApiPropertyOptional({ example: 'sleeper', description: 'Coach type descriptor' }) @IsOptional() @IsString() coachType?: string; + @ApiPropertyOptional({ example: 'seat', description: 'seat | bed | convertible. Determines which arrangement field is used for seat generation.' }) @IsOptional() @IsString() mode?: string; + @ApiPropertyOptional({ example: '2+2', description: 'Seat arrangement for seat/convertible mode. Format: groups separated by +, e.g. "2+2" (4 cols: A/B aisle C/D) or "1+2+1". Used to derive columns, window and aisle flags. Required when mode=seat and totalUnits>0.' }) @IsOptional() @IsString() seatArrangement?: string; + @ApiPropertyOptional({ example: '2+2', description: 'Bed arrangement for bed mode. First number = tiers per berth: 2 โ†’ lower/upper, 3 โ†’ lower/middle/upper. E.g. "2+2" = 2-tier berths. Required when mode=bed and totalUnits>0.' }) @IsOptional() @IsString() bedArrangement?: string; + @ApiPropertyOptional({ example: 60, description: 'Total seat/bed units. When >0, seats are auto-generated from the arrangement on coach creation.' }) @IsOptional() @IsInt() totalUnits?: number; +} + +export class UpdateCoachDto extends PartialType(OmitType(CreateCoachDto, ['coachNumber'] as const)) {} + +export class AssignCoachDto { + @ApiProperty({ example: 'schedule-uuid', description: 'TrainSchedule UUID' }) @IsString() scheduleId: string; + @ApiProperty({ example: 'coach-uuid', description: 'Coach UUID' }) @IsString() coachId: string; + @ApiProperty({ example: 1, description: 'Position in the train consist (1 = first coach)' }) @IsInt() positionNumber: number; + @ApiPropertyOptional({ example: true, description: 'Whether this coach is operational for this schedule' }) @IsOptional() @IsBoolean() isOperational?: boolean; } export class CreateSeatBatchDto { - @ApiProperty() @IsString() coachId: string; - @ApiProperty({ example: 10 }) @IsInt() rows: number; - @ApiProperty({ example: ['A', 'B', 'C', 'D'] }) cols: string[]; + @ApiProperty({ example: 'coach-uuid', description: 'Coach UUID to generate seats for' }) @IsString() coachId: string; + @ApiProperty({ example: 15, description: 'Number of rows to generate' }) @IsInt() rows: number; + @ApiProperty({ example: ['A', 'B', 'C', 'D'], type: [String], description: 'Column labels per row' }) @IsArray() @IsString({ each: true }) cols: string[]; +} + +export class ListCoachesDto { + @ApiPropertyOptional({ example: true, description: 'Filter by active/inactive status. Omit to return all.' }) + @IsOptional() @IsBoolean() isActive?: boolean; + + @ApiPropertyOptional({ example: 'seat', description: 'Filter by mode: seat | bed | convertible' }) + @IsOptional() @IsString() mode?: string; + + @ApiPropertyOptional({ example: 'seat-class-uuid', description: 'Filter by SeatClass UUID' }) + @IsOptional() @IsString() seatClassId?: string; + + @ApiPropertyOptional({ example: 'schedule-uuid', description: 'Filter to coaches assigned to this TrainSchedule UUID' }) + @IsOptional() @IsString() scheduleId?: string; } diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts index 6fa189666..b6fdce100 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts @@ -1,26 +1,258 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; -import { CreateTrainServiceDto, CreateCoachDto, CreateSeatBatchDto } from './fleet.dto'; +import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, CreateSeatBatchDto, ListCoachesDto } from './fleet.dto'; +import { SeatKind } from '@prisma/client'; + +// Parses '2+2' โ†’ [2, 2], '2+2+2' โ†’ [2, 2, 2] +function parseArrangement(arrangement: string): number[] { + return arrangement.split('+').map((n) => parseInt(n, 10)); +} + +// Derives column labels from a seat-mode arrangement string. +// '2+2' โ†’ ['A','B','C','D'] (A/D window, B/C aisle) +// '1+2+1' โ†’ ['A','B','C','D'] +function seatCols(arrangement: string): string[] { + const groups = parseArrangement(arrangement); + const total = groups.reduce((s, n) => s + n, 0); + return Array.from({ length: total }, (_, i) => String.fromCharCode(65 + i)); // A, B, C โ€ฆ +} + +// Returns true if the column index is a window seat given the arrangement groups. +function isWindowCol(colIndex: number, groups: number[]): boolean { + const total = groups.reduce((s, n) => s + n, 0); + return colIndex === 0 || colIndex === total - 1; +} + +// Returns true if the column index is an aisle seat. +function isAisleCol(colIndex: number, groups: number[]): boolean { + let cursor = 0; + for (const g of groups) { + cursor += g; + const leftAisle = cursor - 1; + const rightAisle = cursor; + if (colIndex === leftAisle || colIndex === rightAisle) return true; + } + return false; +} + +// Bed positions for a given tier count: 2 โ†’ lower/upper, 3 โ†’ lower/middle/upper +const BED_POSITIONS: Record = { + 2: ['lower', 'upper'], + 3: ['lower', 'middle', 'upper'], +}; + +type SeatRow = { + coachId: string; + row: number; + col: string; + label: string; + seatNumber: string; + kind: SeatKind; + isWindow: boolean; + isAisle: boolean; + bedPosition?: string; +}; + +function buildSeatSeats(coachId: string, coachLabel: string, arrangement: string, totalUnits: number): SeatRow[] { + const cols = seatCols(arrangement); + const groups = parseArrangement(arrangement); + const seats: SeatRow[] = []; + let row = 1; + while (seats.length < totalUnits) { + for (let ci = 0; ci < cols.length && seats.length < totalUnits; ci++) { + const col = cols[ci]; + seats.push({ + coachId, row, col, + label: `${row}${col}`, + seatNumber: `${coachLabel}${row}${col}`, + kind: SeatKind.STANDARD, + isWindow: isWindowCol(ci, groups), + isAisle: isAisleCol(ci, groups), + }); + } + row++; + } + return seats; +} + +function buildBedSeats(coachId: string, coachLabel: string, arrangement: string, totalUnits: number): SeatRow[] { + // arrangement for beds describes tiers per berth, e.g. '2+2' = 2 lower+upper on each side + // Each compartment number is the row; each tier is the col (L=lower, M=middle, U=upper) + const groups = parseArrangement(arrangement); + const tiersPerSide = groups[0]; // e.g. 2 โ†’ lower+upper + const positions = BED_POSITIONS[tiersPerSide] ?? ['lower', 'upper']; + const tierCols = positions.map((_, i) => String.fromCharCode(65 + i)); // A=lower, B=upper, C=middle + const seats: SeatRow[] = []; + let compartment = 1; + while (seats.length < totalUnits) { + for (let ti = 0; ti < tierCols.length && seats.length < totalUnits; ti++) { + const col = tierCols[ti]; + seats.push({ + coachId, row: compartment, col, + label: `${compartment}${col}`, + seatNumber: `${coachLabel}${compartment}${col}`, + kind: SeatKind.STANDARD, + isWindow: false, + isAisle: false, + bedPosition: positions[ti], + }); + } + compartment++; + } + return seats; +} @Injectable() export class FleetService { constructor(private prisma: PrismaService) {} - getServices() { return this.prisma.trainService.findMany({ include: { trips: { take: 5, orderBy: { departureAt: 'desc' } } } }); } - createService(dto: CreateTrainServiceDto) { return this.prisma.trainService.create({ data: dto }); } - createCoach(dto: CreateCoachDto) { return this.prisma.coach.create({ data: dto }); } + + getTrains() { + return this.prisma.train.findMany({ include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } } }); + } + + createTrain(dto: CreateTrainDto) { return this.prisma.train.create({ data: dto }); } + + async getCoach(id: string) { + const coach = await this.prisma.coach.findUnique({ + where: { id }, + include: { + seatClass: true, + seats: { + orderBy: [{ row: 'asc' }, { col: 'asc' }], + }, + assignments: { + include: { schedule: { include: { originStation: true, destinationStation: true } } }, + orderBy: { schedule: { departureAt: 'desc' } }, + take: 5, + }, + _count: { select: { seats: true, assignments: true } }, + }, + }); + if (!coach) throw new NotFoundException('Coach not found'); + + // Group seats by row to reflect the physical arrangement layout + const rowMap = new Map(); + for (const seat of coach.seats) { + if (!rowMap.has(seat.row)) rowMap.set(seat.row, []); + rowMap.get(seat.row)!.push(seat); + } + + const seatsByRow = Array.from(rowMap.entries()).map(([row, seats]) => ({ row, seats })); + + const seatStatusSummary = { + total: coach.seats.length, + available: coach.seats.filter(s => s.status === 'AVAILABLE').length, + held: coach.seats.filter(s => s.status === 'HELD').length, + booked: coach.seats.filter(s => s.status === 'BOOKED').length, + blocked: coach.seats.filter(s => s.status === 'BLOCKED').length, + }; + + const { seats, ...coachData } = coach; + return { ...coachData, seatsByRow, seatStatusSummary }; + } + + async listCoaches(dto: ListCoachesDto) { + const where: any = {}; + if (dto.isActive !== undefined) where.isActive = dto.isActive; + if (dto.mode) where.mode = dto.mode; + if (dto.seatClassId) where.seatClassId = dto.seatClassId; + if (dto.scheduleId) where.assignments = { some: { scheduleId: dto.scheduleId } }; + + const coaches = await this.prisma.coach.findMany({ + where, + include: { + seatClass: true, + seats: { select: { status: true } }, + _count: { select: { seats: true, assignments: true } }, + }, + orderBy: [{ isActive: 'desc' }, { label: 'asc' }], + }); + + return coaches.map(({ seats, ...coach }) => ({ + ...coach, + seatStatusSummary: { + total: seats.length, + available: seats.filter(s => s.status === 'AVAILABLE').length, + held: seats.filter(s => s.status === 'HELD').length, + booked: seats.filter(s => s.status === 'BOOKED').length, + blocked: seats.filter(s => s.status === 'BLOCKED').length, + }, + })); + } + + async createCoach(dto: CreateCoachDto) { + const mode = dto.mode ?? 'seat'; + const totalUnits = dto.totalUnits ?? 0; + + const isBed = mode === 'bed'; + const arrangement = isBed + ? (dto.bedArrangement ?? dto.seatArrangement ?? '2+2') + : (dto.seatArrangement ?? '2+2'); + + if (totalUnits > 0) { + const groups = parseArrangement(arrangement); + if (groups.some(isNaN)) { + throw new BadRequestException(`Invalid arrangement format "${arrangement}". Use e.g. "2+2" or "2+2+2"`); + } + } + + const coach = await this.prisma.coach.create({ data: dto }); + + if (totalUnits > 0) { + const seats = isBed + ? buildBedSeats(coach.id, coach.label, arrangement, totalUnits) + : buildSeatSeats(coach.id, coach.label, arrangement, totalUnits); + await this.prisma.seat.createMany({ data: seats, skipDuplicates: true }); + } + + return this.prisma.coach.findUnique({ + where: { id: coach.id }, + include: { seatClass: true, _count: { select: { seats: true } } }, + }); + } + + async updateCoach(id: string, dto: UpdateCoachDto) { + const coach = await this.prisma.coach.findUnique({ where: { id } }); + if (!coach) throw new NotFoundException('Coach not found'); + return this.prisma.coach.update({ where: { id }, data: dto }); + } + + async assignCoach(dto: AssignCoachDto) { + const [schedule, coach] = await Promise.all([ + this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } }), + this.prisma.coach.findUnique({ where: { id: dto.coachId } }), + ]); + if (!schedule) throw new NotFoundException('Schedule not found'); + if (!coach) throw new NotFoundException('Coach not found'); + return this.prisma.coachAssignment.create({ data: dto }); + } + + async removeAssignment(id: string) { + const assignment = await this.prisma.coachAssignment.findUnique({ where: { id } }); + if (!assignment) throw new NotFoundException('Assignment not found'); + return this.prisma.coachAssignment.delete({ where: { id } }); + } + async createSeatBatch(dto: CreateSeatBatchDto) { const coach = await this.prisma.coach.findUnique({ where: { id: dto.coachId } }); if (!coach) throw new NotFoundException('Coach not found'); const seats = []; - for (let row = 1; row <= dto.rows; row++) for (const col of dto.cols) seats.push({ coachId: dto.coachId, row, col, label: `${row}${col}` }); + for (let row = 1; row <= dto.rows; row++) { + for (const col of dto.cols) { + seats.push({ coachId: dto.coachId, row, col, label: `${row}${col}`, seatNumber: `${coach.label}${row}${col}` }); + } + } await this.prisma.seat.createMany({ data: seats, skipDuplicates: true }); return { created: seats.length }; } + async getAnalytics() { - const [totalServices, totalTrips, totalSeats, bookedSeats] = await Promise.all([ - this.prisma.trainService.count(), this.prisma.trip.count(), - this.prisma.seat.count(), this.prisma.seat.count({ where: { status: 'BOOKED' } }), + const [totalTrains, totalSchedules, totalSeats, bookedSeats] = await Promise.all([ + this.prisma.train.count(), + this.prisma.trainSchedule.count(), + this.prisma.seat.count(), + this.prisma.seat.count({ where: { status: 'BOOKED' } }), ]); - return { totalServices, totalTrips, totalSeats, bookedSeats, occupancyRate: totalSeats > 0 ? +((bookedSeats / totalSeats) * 100).toFixed(2) : 0 }; + return { totalTrains, totalSchedules, totalSeats, bookedSeats, occupancyRate: totalSeats > 0 ? +((bookedSeats / totalSeats) * 100).toFixed(2) : 0 }; } } diff --git a/apps/edr-passenger-api/src/modules/fraud/fraud.controller.ts b/apps/edr-passenger-api/src/modules/fraud/fraud.controller.ts new file mode 100644 index 000000000..4056b4259 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/fraud/fraud.controller.ts @@ -0,0 +1,74 @@ +import { Controller, Get, Post, Body, Query, UseGuards, Logger } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { FraudService, FraudRuleConfig } from './fraud.service'; +import { IamGuard, IamRoles } from '../../common/iam-adapter'; +import { UserRole } from '@prisma/client'; + +@ApiTags('Fraud Detection') +@Controller('fraud') +@UseGuards(IamGuard) +@ApiBearerAuth('IAM-auth') +export class FraudController { + private readonly logger = new Logger(FraudController.name); + + constructor(private fraudService: FraudService) {} + + /** + * Get fraud alerts + */ + @Get('alerts') + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'Get fraud alerts' }) + async getAlerts( + @Query('userId') userId?: string, + @Query('limit') limit?: string, + @Query('offset') offset?: string, + ) { + const alerts = await this.fraudService.getAlerts(userId, parseInt(limit || '100'), parseInt(offset || '0')); + return { data: alerts, total: alerts.length }; + } + + /** + * Get fraud rules + */ + @Get('rules') + @IamRoles('ADMIN') + @ApiOperation({ summary: 'Get fraud detection rules' }) + async getRules() { + const rules = await this.fraudService.getRules(); + return { data: rules }; + } + + /** + * Create or update fraud rule + */ + @Post('rules') + @IamRoles('ADMIN') + @ApiOperation({ summary: 'Create or update fraud rule' }) + async upsertRule(@Body() body: { type: string; config: FraudRuleConfig }) { + const rule = await this.fraudService.upsertRule(body.type, body.config); + return { data: rule, message: 'Rule updated successfully' }; + } + + /** + * Block user temporarily + */ + @Post('actions/block') + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'Block user temporarily' }) + async blockUser(@Body() body: { userId: string; durationMinutes: number }) { + await this.fraudService.blockUserTemporarily(body.userId, body.durationMinutes); + return { message: `User blocked for ${body.durationMinutes} minutes` }; + } + + /** + * Unblock user + */ + @Post('actions/unblock') + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'Unblock user' }) + async unblockUser(@Body() body: { userId: string }) { + await this.fraudService.unblockUser(body.userId); + return { message: 'User unblocked' }; + } +} diff --git a/apps/edr-passenger-api/src/modules/fraud/fraud.module.ts b/apps/edr-passenger-api/src/modules/fraud/fraud.module.ts new file mode 100644 index 000000000..a95078578 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/fraud/fraud.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { FraudService } from './fraud.service'; +import { FraudController } from './fraud.controller'; + +@Module({ + imports: [HttpModule], + providers: [FraudService], + controllers: [FraudController], + exports: [FraudService], +}) +export class FraudModule {} diff --git a/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts b/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts new file mode 100644 index 000000000..7c3b66e6b --- /dev/null +++ b/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts @@ -0,0 +1,252 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { PrismaService } from '../../common/prisma.service'; + +export interface FraudRuleConfig { + type: 'VELOCITY' | 'HIGH_VALUE' | 'FAILED_PAYMENTS' | 'MULTIPLE_METHODS'; + enabled: boolean; + threshold: number; + timeWindowMinutes?: number; + blockDurationMinutes?: number; +} + +@Injectable() +export class FraudService { + private readonly logger = new Logger(FraudService.name); + + constructor(private prisma: PrismaService) {} + + /** + * Evaluate fraud rules and create alerts if triggered + */ + async evaluateRules( + userId: string, + eventType: 'booking.created' | 'payment.failed' | 'auth.login.failed', + context: Record, + ): Promise<{ triggered: boolean; rules: string[] }> { + const triggeredRules: string[] = []; + const user = await this.prisma.user.findUnique({ where: { id: userId } }); + + if (!user) return { triggered: false, rules: [] }; + + // Check velocity rule (multiple bookings in short time) + if (eventType === 'booking.created') { + const velocityTriggered = await this.checkVelocityRule(userId); + if (velocityTriggered) { + triggeredRules.push('VELOCITY'); + } + + // Check high-value booking + const amount = (context.amountMinor as number) || 0; + const highValueTriggered = await this.checkHighValueRule(amount); + if (highValueTriggered) { + triggeredRules.push('HIGH_VALUE'); + } + } + + // Check repeated failed payments + if (eventType === 'payment.failed') { + const failedPaymentTriggered = await this.checkFailedPaymentRule(userId); + if (failedPaymentTriggered) { + triggeredRules.push('FAILED_PAYMENTS'); + } + } + + // Create alert if rules triggered + if (triggeredRules.length > 0) { + await this.createFraudAlert(userId, eventType, triggeredRules, context); + return { triggered: true, rules: triggeredRules }; + } + + return { triggered: false, rules: [] }; + } + + /** + * Check velocity rule: X bookings in Y minutes + */ + private async checkVelocityRule(userId: string): Promise { + const rule = await this.prisma.fraudRule.findFirst({ + where: { type: 'VELOCITY', enabled: true }, + }); + + if (!rule) return false; + + const timeWindowMinutes = (rule.config as any)?.timeWindowMinutes || 30; + const threshold = rule.threshold; + + const bookingCount = await this.prisma.booking.count({ + where: { + passengerId: userId, + createdAt: { + gte: new Date(Date.now() - timeWindowMinutes * 60 * 1000), + }, + }, + }); + + return bookingCount > threshold; + } + + /** + * Check high-value booking rule + */ + private async checkHighValueRule(amountMinor: number): Promise { + const rule = await this.prisma.fraudRule.findFirst({ + where: { type: 'HIGH_VALUE', enabled: true }, + }); + + if (!rule) return false; + + // threshold is in ETB (convert minor units to ETB) + const amountEtb = amountMinor / 100; + return amountEtb > rule.threshold; + } + + /** + * Check failed payment rule: X failed attempts in Y minutes + */ + private async checkFailedPaymentRule(userId: string): Promise { + const rule = await this.prisma.fraudRule.findFirst({ + where: { type: 'FAILED_PAYMENTS', enabled: true }, + }); + + if (!rule) return false; + + const timeWindowMinutes = (rule.config as any)?.timeWindowMinutes || 60; + const threshold = rule.threshold; + + const failedCount = await this.prisma.paymentIntent.count({ + where: { + booking: { passengerId: userId }, + status: 'FAILED', + updatedAt: { + gte: new Date(Date.now() - timeWindowMinutes * 60 * 1000), + }, + }, + }); + + return failedCount > threshold; + } + + /** + * Create a fraud alert + */ + private async createFraudAlert( + userId: string, + eventType: string, + triggeredRules: string[], + context: Record, + ): Promise { + const alert = await this.prisma.fraudAlert.create({ + data: { + userId, + eventType, + triggeredRules, + context: context as any, + severity: triggeredRules.length > 1 ? 'HIGH' : 'MEDIUM', + }, + }); + + this.logger.warn(`Fraud alert created: ${alert.id} for user ${userId} - rules: ${triggeredRules.join(', ')}`); + + // Trigger blocking if needed + if (triggeredRules.includes('HIGH_VALUE') || triggeredRules.length > 1) { + await this.blockUserTemporarily(userId, 30); // Block for 30 minutes + } + } + + /** + * Block user temporarily + */ + async blockUserTemporarily(userId: string, durationMinutes: number): Promise { + const blockedUntil = new Date(Date.now() + durationMinutes * 60 * 1000); + await this.prisma.user.update({ + where: { id: userId }, + data: { blockedUntil }, + }); + this.logger.warn(`User ${userId} blocked until ${blockedUntil.toISOString()}`); + } + + /** + * Unblock user + */ + async unblockUser(userId: string): Promise { + await this.prisma.user.update({ + where: { id: userId }, + data: { blockedUntil: null }, + }); + this.logger.log(`User ${userId} unblocked`); + } + + /** + * Get all fraud alerts + */ + async getAlerts(userId?: string, limit = 100, offset = 0) { + return this.prisma.fraudAlert.findMany({ + where: userId ? { userId } : {}, + orderBy: { createdAt: 'desc' }, + take: limit, + skip: offset, + }); + } + + /** + * Create or update a fraud rule + */ + async upsertRule( + type: string, + config: FraudRuleConfig, + ) { + return this.prisma.fraudRule.upsert({ + where: { type: type as any }, + update: { + enabled: config.enabled, + threshold: config.threshold, + config: config as any, + }, + create: { + type: type as any, + enabled: config.enabled, + threshold: config.threshold, + config: config as any, + }, + }); + } + + /** + * Get all fraud rules + */ + async getRules() { + return this.prisma.fraudRule.findMany(); + } + + /** + * Event listener for booking created + */ + @OnEvent('booking.created') + async onBookingCreated(payload: { booking: any }) { + await this.evaluateRules(payload.booking.passengerId, 'booking.created', { + bookingId: payload.booking.id, + amountMinor: payload.booking.totalMinor, + }); + } + + /** + * Event listener for payment failed + */ + @OnEvent('payment.failed') + async onPaymentFailed(payload: { intentId: string; userId: string }) { + await this.evaluateRules(payload.userId, 'payment.failed', { + intentId: payload.intentId, + }); + } + + /** + * Event listener for auth login failed + */ + @OnEvent('auth.login.failed') + async onLoginFailed(payload: { userId: string; email: string }) { + await this.evaluateRules(payload.userId, 'auth.login.failed', { + email: payload.email, + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/live/live.controller.ts b/apps/edr-passenger-api/src/modules/live/live.controller.ts index a34e92e35..13058a1cb 100644 --- a/apps/edr-passenger-api/src/modules/live/live.controller.ts +++ b/apps/edr-passenger-api/src/modules/live/live.controller.ts @@ -8,9 +8,9 @@ import { JwtGuard } from '../../common/jwt.guard'; @Controller('live') export class LiveController { constructor(private service: LiveService) {} - @Get('trips/:tripId') @ApiOperation({ summary: 'Get live status for a trip' }) getTripLiveStatus(@Param('tripId') id: string) { return this.service.getTripLiveStatus(id); } - @Get('trips/:tripId/stops') @ApiOperation({ summary: 'Get stop timeline for a trip' }) getStopTimeline(@Param('tripId') id: string) { return this.service.getStopTimeline(id); } - @Patch('trips/:tripId/status')@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Update live trip status (staff/system)' }) updateLiveStatus(@Param('tripId') id: string, @Body() dto: UpdateLiveStatusDto) { return this.service.updateLiveStatus(id, dto); } + @Get('schedules/:scheduleId') @ApiOperation({ summary: 'Get live status for a schedule' }) getTripLiveStatus(@Param('scheduleId') id: string) { return this.service.getTripLiveStatus(id); } + @Get('schedules/:scheduleId/stops') @ApiOperation({ summary: 'Get stop timeline for a schedule' }) getStopTimeline(@Param('scheduleId') id: string) { return this.service.getStopTimeline(id); } + @Patch('schedules/:scheduleId/status') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Update live schedule status (staff/system)' }) updateLiveStatus(@Param('scheduleId') id: string, @Body() dto: UpdateLiveStatusDto) { return this.service.updateLiveStatus(id, dto); } @Get('crowd-signals') @ApiOperation({ summary: 'Get station crowd signals' }) getCrowdSignals() { return this.service.getStationCrowdSignals(); } @Get('weather-alerts') @ApiOperation({ summary: 'Get active weather alerts' }) getWeatherAlerts() { return this.service.getWeatherAlerts(); } } diff --git a/apps/edr-passenger-api/src/modules/live/live.service.ts b/apps/edr-passenger-api/src/modules/live/live.service.ts index bfc538059..7f0a858dc 100644 --- a/apps/edr-passenger-api/src/modules/live/live.service.ts +++ b/apps/edr-passenger-api/src/modules/live/live.service.ts @@ -5,29 +5,31 @@ import { PrismaService } from '../../common/prisma.service'; export class LiveService { constructor(private prisma: PrismaService) {} - async getTripLiveStatus(tripId: string) { - const trip = await this.prisma.trip.findUnique({ - where: { id: tripId }, - include: { service: true, originStation: true, destinationStation: true, liveStatus: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } }, + async getTripLiveStatus(scheduleId: string) { + const schedule = await this.prisma.trainSchedule.findUnique({ + where: { id: scheduleId }, + include: { train: true, originStation: true, destinationStation: true, liveStatus: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } }, }); - if (!trip) throw new NotFoundException('Trip not found'); - const live = trip.liveStatus; - const nextStop = trip.stopTimes.find((s) => s.status === 'UPCOMING' || s.status === 'APPROACHING'); + if (!schedule) throw new NotFoundException('Schedule not found'); + const live = schedule.liveStatus; + const nextStop = schedule.stopTimes.find((s) => s.status === 'UPCOMING' || s.status === 'APPROACHING'); return { - tripId: trip.id, trainName: trip.service.name, - fromStationName: trip.originStation.name, toStationName: trip.destinationStation.name, - state: live?.state ?? trip.status, currentLocationLabel: live?.currentLocationLabel, + scheduleId: schedule.id, trainName: schedule.train.name, + fromStationName: schedule.originStation.name, toStationName: schedule.destinationStation.name, + state: live?.state ?? schedule.status, currentLocationLabel: live?.currentLocationLabel, progressPercent: live?.progressPercent ?? 0, delayMinutes: live?.delayMinutes ?? 0, currentSpeedKph: live?.currentSpeedKph, platformLabel: live?.platformLabel, - nextStopStationName: nextStop?.station.name, updatedAt: live?.updatedAt ?? trip.departureAt, + nextStopStationName: nextStop?.station.name, updatedAt: live?.updatedAt ?? schedule.departureAt, }; } - updateLiveStatus(tripId: string, data: any) { - return this.prisma.tripLiveStatus.upsert({ where: { tripId }, update: data, create: { tripId, state: data.state ?? 'SCHEDULED', ...data } }); + updateLiveStatus(scheduleId: string, data: any) { + return this.prisma.tripLiveStatus.upsert({ where: { scheduleId }, update: data, create: { scheduleId, state: data.state ?? 'SCHEDULED', ...data } }); } - getStopTimeline(tripId: string) { return this.prisma.tripStopTime.findMany({ where: { tripId }, include: { station: true }, orderBy: { sequence: 'asc' } }); } + getStopTimeline(scheduleId: string) { + return this.prisma.tripStopTime.findMany({ where: { scheduleId }, include: { station: true }, orderBy: { sequence: 'asc' } }); + } getStationCrowdSignals() { return this.prisma.stationCrowdSignal.findMany({ include: { station: true } }); } diff --git a/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts b/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts new file mode 100644 index 000000000..c7db1a0a4 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts @@ -0,0 +1,216 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as sgMail from '@sendgrid/mail'; +import { HttpService } from '@nestjs/axios'; +import { firstValueFrom } from 'rxjs'; + +export interface NotificationChannel { + send(recipient: string, subject: string, body: string, context?: Record): Promise; +} + +@Injectable() +export class EmailAdapter implements NotificationChannel { + private readonly logger = new Logger(EmailAdapter.name); + + constructor(private readonly config: ConfigService) { + const apiKey = this.config.get('SENDGRID_API_KEY'); + if (apiKey) { + sgMail.setApiKey(apiKey); + this.logger.log('SendGrid Email adapter initialized'); + } else { + this.logger.warn('SENDGRID_API_KEY not configured - emails will be logged only'); + } + } + + async send( + recipient: string, + subject: string, + body: string, + context?: Record, + ): Promise { + const apiKey = this.config.get('SENDGRID_API_KEY'); + const fromEmail = this.config.get('SENDGRID_FROM_EMAIL') || 'noreply@edr-platform.com'; + + if (!apiKey) { + this.logger.log(`[EMAIL MOCK] To: ${recipient} | Subject: ${subject} | Body: ${body.substring(0, 100)}`); + return true; + } + + try { + const msg: sgMail.MailDataRequired = { + to: recipient, + from: fromEmail, + subject, + text: body, + html: this.formatHtml(body, context), + }; + + await sgMail.send(msg); + this.logger.log(`Email sent successfully to ${recipient}`); + return true; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`Failed to send email to ${recipient}: ${message}`); + return false; + } + } + + private formatHtml(body: string, context?: Record): string { + const contextHtml = context + ? `
+ ${JSON.stringify(context, null, 2)} +
` + : ''; + + return ` + + + + + + + +
+
+

Ethio-Djibouti Railway

+
+
+ ${body.replace(/\n/g, '
')} + ${contextHtml} +
+ +
+ + + `; + } +} + +@Injectable() +export class SmsAdapter implements NotificationChannel { + private readonly logger = new Logger(SmsAdapter.name); + + constructor( + private readonly config: ConfigService, + private readonly http: HttpService, + ) { + const provider = this.config.get('SMS_PROVIDER'); + this.logger.log(`SMS adapter initialized with provider: ${provider || 'MOCK'}`); + } + + async send( + recipient: string, + subject: string, + body: string, + _context?: Record, + ): Promise { + const provider = this.config.get('SMS_PROVIDER'); + const apiKey = this.config.get('SMS_API_KEY'); + + if (!provider || !apiKey) { + this.logger.log(`[SMS MOCK] To: ${recipient} | Message: ${body.substring(0, 100)}`); + return true; + } + + try { + switch (provider.toLowerCase()) { + case 'twilio': + return await this.sendViaTwilio(recipient, body); + case 'africastalking': + return await this.sendViaAfricasTalking(recipient, body); + default: + this.logger.warn(`Unknown SMS provider: ${provider}`); + return false; + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`Failed to send SMS to ${recipient}: ${message}`); + return false; + } + } + + private async sendViaTwilio(to: string, body: string): Promise { + const accountSid = this.config.get('TWILIO_ACCOUNT_SID'); + const authToken = this.config.get('TWILIO_AUTH_TOKEN'); + const fromNumber = this.config.get('TWILIO_FROM_NUMBER'); + + const url = `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`; + const auth = Buffer.from(`${accountSid}:${authToken}`).toString('base64'); + + const response = await firstValueFrom( + this.http.post( + url, + new URLSearchParams({ + To: to, + From: fromNumber || '', + Body: body, + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': `Basic ${auth}`, + }, + }, + ), + ); + + return response.status === 201; + } + + private async sendViaAfricasTalking(to: string, body: string): Promise { + const apiKey = this.config.get('SMS_API_KEY'); + const username = this.config.get('AFRICASTALKING_USERNAME'); + const from = this.config.get('AFRICASTALKING_FROM'); + + const url = 'https://api.africastalking.com/version1/messaging'; + + const response = await firstValueFrom( + this.http.post( + url, + new URLSearchParams({ + username: username || '', + to, + message: body, + from: from || '', + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'apiKey': apiKey || '', + }, + }, + ), + ); + + return response.status === 201; + } +} + +@Injectable() +export class PushAdapter implements NotificationChannel { + private readonly logger = new Logger(PushAdapter.name); + + constructor(private readonly config: ConfigService) { + this.logger.log('Push notification adapter initialized'); + } + + async send( + recipient: string, + subject: string, + body: string, + context?: Record, + ): Promise { + // Push notifications would typically use FCM/APNS + // For now, just log + this.logger.log(`[PUSH MOCK] To: ${recipient} | Title: ${subject} | Body: ${body.substring(0, 100)}`); + return true; + } +} diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts index 076c023a1..9b363c04f 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts @@ -1,7 +1,9 @@ -import { Controller, Get, Param, Patch, UseGuards } from '@nestjs/common'; +import { Controller, Get, Param, Patch, Post, Body, UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { NotificationsService } from './notifications.service'; import { JwtGuard } from '../../common/jwt.guard'; +import { IamGuard, IamRoles } from '../../common/iam-adapter'; +import { TestNotificationDto } from './notifications.dto'; @ApiTags('Notifications') @Controller('notifications') @@ -12,13 +14,32 @@ export class NotificationsController { @Get(':passengerId') @ApiOperation({ summary: 'Get notifications for passenger' }) - getForPassenger(@Param('passengerId') id: string) { return this.service.getForPassenger(id); } + getForPassenger(@Param('passengerId') id: string) { + return this.service.getForPassenger(id); + } @Patch(':id/read') @ApiOperation({ summary: 'Mark notification as read' }) - markRead(@Param('id') id: string) { return this.service.markRead(id); } + markRead(@Param('id') id: string) { + return this.service.markRead(id); + } @Patch(':passengerId/read-all') @ApiOperation({ summary: 'Mark all notifications as read' }) - markAllRead(@Param('passengerId') id: string) { return this.service.markAllRead(id); } + markAllRead(@Param('passengerId') id: string) { + return this.service.markAllRead(id); + } + + @Post('test') + @UseGuards(IamGuard) + @IamRoles('ADMIN', 'STAFF') + @ApiOperation({ summary: 'Test notification delivery (Admin only)' }) + async testNotification(@Body() dto: TestNotificationDto) { + return this.service.send( + dto.templateKey, + dto.recipient, + dto.context, + dto.channels as any, + ); + } } diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.dto.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.dto.ts index 07bd9dd40..e55535a2a 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.dto.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsEnum, IsOptional } from 'class-validator'; +import { IsString, IsEnum, IsOptional, IsArray } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export enum NotificationCategoryEnum { @@ -17,3 +17,21 @@ export class SendNotificationDto { @ApiPropertyOptional({ example: 'edr://tickets/tkt_01' }) @IsOptional() @IsString() deepLink?: string; @ApiPropertyOptional() @IsOptional() metadata?: Record; } + +export class TestNotificationDto { + @ApiProperty({ example: 'booking.created' }) + @IsString() + templateKey: string; + + @ApiProperty({ example: 'user@example.com' }) + @IsString() + recipient: string; + + @ApiProperty({ example: { bookingRef: 'EDR123456', passengerName: 'John Doe' } }) + context: Record; + + @ApiPropertyOptional({ example: ['EMAIL', 'SMS', 'IN_APP'] }) + @IsOptional() + @IsArray() + channels?: string[]; +} diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts index 8a1322a50..b4c28405f 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts @@ -1,6 +1,13 @@ import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; import { NotificationsController } from './notifications.controller'; import { NotificationsService } from './notifications.service'; +import { EmailAdapter, SmsAdapter, PushAdapter } from './notification.adapters'; -@Module({ controllers: [NotificationsController], providers: [NotificationsService], exports: [NotificationsService] }) +@Module({ + imports: [HttpModule.register({ timeout: 10_000 })], + controllers: [NotificationsController], + providers: [NotificationsService, EmailAdapter, SmsAdapter, PushAdapter], + exports: [NotificationsService], +}) export class NotificationsModule {} diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts index a92c4c71f..e793a1322 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts @@ -1,45 +1,263 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { PrismaService } from '../../common/prisma.service'; import { SendNotificationDto, NotificationCategoryEnum } from './notifications.dto'; -import * as sgMail from '@sendgrid/mail'; +import { EmailAdapter, SmsAdapter, PushAdapter, NotificationChannel } from './notification.adapters'; + +export type NotificationChannelType = 'EMAIL' | 'SMS' | 'PUSH' | 'IN_APP'; @Injectable() export class NotificationsService { - constructor(private prisma: PrismaService) { - if (process.env.SENDGRID_API_KEY) sgMail.setApiKey(process.env.SENDGRID_API_KEY); + private readonly logger = new Logger(NotificationsService.name); + private readonly channels: Map; + + constructor( + private prisma: PrismaService, + private emailAdapter: EmailAdapter, + private smsAdapter: SmsAdapter, + private pushAdapter: PushAdapter, + ) { + this.channels = new Map([ + ['EMAIL', this.emailAdapter as NotificationChannel], + ['SMS', this.smsAdapter as NotificationChannel], + ['PUSH', this.pushAdapter as NotificationChannel], + ]); } - private sanitize(value: string): string { - return value.replace(/[\r\n]/g, ' ').replace(/[<>&"']/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c] ?? c)); + /** + * Send notification using template key and context + * @param templateKey - Template code from NotificationTemplate table + * @param recipient - User/Passenger ID or email/phone + * @param context - Variables to interpolate in template + * @param channels - Optional array of channels to use (defaults to user preferences) + */ + async send( + templateKey: string, + recipient: string, + context: Record, + channels?: NotificationChannelType[], + ): Promise<{ sent: boolean; channels: string[] }> { + const template = await this.prisma.notificationTemplate.findUnique({ + where: { code: templateKey }, + }); + + if (!template || !template.active) { + this.logger.warn(`Template ${templateKey} not found or inactive`); + return { sent: false, channels: [] }; + } + + const { subject, body } = this.interpolate(template, context); + const targetChannels = channels || await this.getUserPreferredChannels(recipient); + const sentChannels: string[] = []; + + // Always create in-app notification + if (targetChannels.includes('IN_APP')) { + await this.createInAppNotification(recipient, subject, body, context); + sentChannels.push('IN_APP'); + } + + // Send via other channels + for (const channelType of targetChannels) { + if (channelType === 'IN_APP') continue; + + const adapter = this.channels.get(channelType); + if (!adapter) { + this.logger.warn(`No adapter for channel: ${channelType}`); + continue; + } + + const recipientAddress = await this.getRecipientAddress(recipient, channelType); + if (!recipientAddress) { + this.logger.warn(`No ${channelType} address for recipient: ${recipient}`); + continue; + } + + const success = await adapter.send(recipientAddress, subject, body, context); + if (success) { + sentChannels.push(channelType); + } + } + + return { sent: sentChannels.length > 0, channels: sentChannels }; } - async send(dto: SendNotificationDto) { - const notification = await this.prisma.notification.create({ data: { passengerId: dto.passengerId, title: dto.title, body: dto.body, category: dto.category as any, deepLink: dto.deepLink, metadata: dto.metadata } }); - const passenger = await this.prisma.passenger.findUnique({ where: { id: dto.passengerId }, include: { user: true } }); - if (passenger?.user) await this.sendEmail(passenger.user.email, this.sanitize(dto.title), this.sanitize(dto.body)); + /** + * Legacy method for backward compatibility + */ + async sendDirect(dto: SendNotificationDto) { + const notification = await this.prisma.notification.create({ + data: { + passengerId: dto.passengerId, + title: dto.title, + body: dto.body, + category: dto.category as any, + deepLink: dto.deepLink, + metadata: dto.metadata, + }, + }); + + const passenger = await this.prisma.passenger.findUnique({ + where: { id: dto.passengerId }, + include: { user: true }, + }); + + if (passenger?.user) { + await this.emailAdapter.send( + passenger.user.email, + this.sanitize(dto.title), + this.sanitize(dto.body), + ); + } + return notification; } - getForPassenger(passengerId: string) { return this.prisma.notification.findMany({ where: { passengerId }, orderBy: { createdAt: 'desc' }, take: 50 }); } + private async createInAppNotification( + recipient: string, + title: string, + body: string, + context: Record, + ): Promise { + // Try to find passenger by ID or email + let passengerId = recipient; - markRead(id: string) { return this.prisma.notification.update({ where: { id }, data: { read: true } }); } + if (!recipient.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) { + const user = await this.prisma.user.findFirst({ + where: { + OR: [{ email: recipient }, { phone: recipient }], + }, + include: { passenger: true }, + }); + if (user?.passenger) { + passengerId = user.passenger.id; + } else { + this.logger.warn(`Could not find passenger for recipient: ${recipient}`); + return; + } + } - async markAllRead(passengerId: string) { await this.prisma.notification.updateMany({ where: { passengerId, read: false }, data: { read: true } }); return { updated: true }; } + await this.prisma.notification.create({ + data: { + passengerId, + title, + body, + category: (context.category as any) || 'SYSTEM', + deepLink: context.deepLink as string, + metadata: context as any, + }, + }); + } + + private interpolate( + template: { subject?: string | null; bodyTemplate: string }, + context: Record, + ): { subject: string; body: string } { + const subject = template.subject || 'Notification'; + let body = template.bodyTemplate; + + // Simple template interpolation: {{variable}} + for (const [key, value] of Object.entries(context)) { + const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g'); + body = body.replace(regex, String(value)); + } + + return { subject, body }; + } + + private async getUserPreferredChannels(recipient: string): Promise { + const user = await this.prisma.user.findFirst({ + where: { + OR: [{ id: recipient }, { email: recipient }, { phone: recipient }], + }, + include: { preferences: true }, + }); + + if (!user?.preferences) { + return ['IN_APP', 'EMAIL']; + } + + const channels: NotificationChannelType[] = ['IN_APP']; + if (user.preferences.emailEnabled) channels.push('EMAIL'); + if (user.preferences.smsEnabled) channels.push('SMS'); + if (user.preferences.pushEnabled) channels.push('PUSH'); + + return channels; + } + + private async getRecipientAddress( + recipient: string, + channel: NotificationChannelType, + ): Promise { + const user = await this.prisma.user.findFirst({ + where: { + OR: [{ id: recipient }, { email: recipient }, { phone: recipient }], + }, + }); + + if (!user) return null; + + switch (channel) { + case 'EMAIL': + return user.email; + case 'SMS': + return user.phone; + case 'PUSH': + // Would need to fetch device push token + return user.id; + default: + return null; + } + } + + private sanitize(value: string): string { + return value + .replace(/[\r\n]/g, ' ') + .replace(/[<>&"']/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c] ?? c)); + } + + getForPassenger(passengerId: string) { + return this.prisma.notification.findMany({ + where: { passengerId }, + orderBy: { createdAt: 'desc' }, + take: 50, + }); + } + + markRead(id: string) { + return this.prisma.notification.update({ where: { id }, data: { read: true } }); + } + + async markAllRead(passengerId: string) { + await this.prisma.notification.updateMany({ + where: { passengerId, read: false }, + data: { read: true }, + }); + return { updated: true }; + } @OnEvent('booking.created') async onBookingCreated(payload: any) { - await this.send({ passengerId: payload.booking.passengerId, title: 'Booking Created', body: `Booking ${payload.booking.bookingRef} created. Complete payment within 15 minutes.`, category: NotificationCategoryEnum.BOOKING, deepLink: `edr://bookings/${payload.booking.bookingRef}`, metadata: { bookingRef: payload.booking.bookingRef } }); + await this.send( + 'booking.created', + payload.booking.passengerId, + { + bookingRef: payload.booking.bookingRef, + category: 'BOOKING', + deepLink: `edr://bookings/${payload.booking.bookingRef}`, + }, + ); } @OnEvent('payment.succeeded') async onPaymentSucceeded(payload: any) { - await this.send({ passengerId: payload.booking.passengerId, title: 'Payment Successful', body: `Your ticket for ${payload.booking.bookingRef} is confirmed. Have a great journey!`, category: NotificationCategoryEnum.PAYMENT, deepLink: `edr://tickets/${payload.booking.bookingRef}`, metadata: { bookingRef: payload.booking.bookingRef } }); - } - - private async sendEmail(to: string, subject: string, text: string) { - if (!process.env.SENDGRID_API_KEY) { console.log(`[EMAIL] To: ${to} | Subject: ${subject}`); return; } - try { await sgMail.send({ to, from: process.env.SENDGRID_FROM_EMAIL || 'noreply@edr-platform.com', subject, text }); } - catch (e) { console.error('[EMAIL] Send error:', String(e instanceof Error ? e.message : e).replace(/[\r\n<>&"']/g, ' ')); } + await this.send( + 'payment.succeeded', + payload.booking.passengerId, + { + bookingRef: payload.booking.bookingRef, + category: 'PAYMENT', + deepLink: `edr://tickets/${payload.booking.bookingRef}`, + }, + ); } } \ No newline at end of file diff --git a/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts b/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts index 54222751e..a454fdd70 100644 --- a/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts +++ b/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts @@ -11,7 +11,7 @@ export class PassengersService { where: { id: passengerId }, include: { user: { select: { fullName: true, email: true, phone: true } }, - bookings: { orderBy: { createdAt: 'desc' }, take: 10, include: { trip: { include: { originStation: true, destinationStation: true, service: true } }, seats: { include: { seat: { include: { coach: true } } } } } }, + bookings: { orderBy: { createdAt: 'desc' }, take: 10, include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: { include: { seatClass: true } } } } } } } }, loyalty: true, wallet: true, travelerProfiles: true, savedRoutes: true, }, }); @@ -25,12 +25,12 @@ export class PassengersService { bookings: p.bookings.map((b) => ({ id: b.id, bookingRef: b.bookingRef, status: b.status, totalFare: b.totalMinor / 100, createdAt: b.createdAt, trip: { - number: b.trip.service.number, - origin: { id: b.trip.originStation.id, name: b.trip.originStation.name, code: b.trip.originStation.code, city: b.trip.originStation.city }, - destination: { id: b.trip.destinationStation.id, name: b.trip.destinationStation.name, code: b.trip.destinationStation.code, city: b.trip.destinationStation.city }, - departureAt: b.trip.departureAt, + number: b.schedule.train.number, + origin: { id: b.schedule.originStation.id, name: b.schedule.originStation.name, code: b.schedule.originStation.code, city: b.schedule.originStation.city }, + destination: { id: b.schedule.destinationStation.id, name: b.schedule.destinationStation.name, code: b.schedule.destinationStation.code, city: b.schedule.destinationStation.city }, + departureAt: b.schedule.departureAt, }, - passengers: b.seats.map((bs) => ({ fullName: bs.passengerName, seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.serviceClass } })), + passengers: b.seats.map((bs) => ({ fullName: bs.passengerName, seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.seatClass?.name ?? 'N/A' } })), })), }; } diff --git a/apps/edr-passenger-api/src/modules/payments/payments.controller.ts b/apps/edr-passenger-api/src/modules/payments/payments.controller.ts index b153ebf80..9e1689df9 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.controller.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.controller.ts @@ -11,6 +11,7 @@ import { JwtGuard } from '../../common/jwt.guard'; export class PaymentsController { constructor(private service: PaymentsService) {} @Post('initiate') @ApiOperation({ summary: 'Initiate payment for a booking' }) initiatePayment(@Body() dto: InitiatePaymentDto) { return this.service.initiatePayment(dto); } + @Get('intents/:bookingId') @ApiOperation({ summary: 'Get payment intent status for a booking' }) getIntent(@Param('bookingId') bookingId: string) { return this.service.getIntentByBookingId(bookingId); } @Post('refund') @ApiOperation({ summary: 'Refund a confirmed booking' }) refund(@Body() dto: RefundDto) { return this.service.refund(dto); } @Post('methods') @ApiOperation({ summary: 'Add a payment method' }) addMethod(@Body() dto: AddPaymentMethodDto) { return this.service.addPaymentMethod(dto); } @Get('methods/:userId') @ApiOperation({ summary: 'Get payment methods for user' }) getMethods(@Param('userId') userId: string) { return this.service.getPaymentMethods(userId); } diff --git a/apps/edr-passenger-api/src/modules/payments/payments.dto.ts b/apps/edr-passenger-api/src/modules/payments/payments.dto.ts index 798e4751d..adeee8b22 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.dto.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.dto.ts @@ -1,5 +1,6 @@ import { IsString, IsEnum, IsOptional } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { PaymentIntentStatus } from '@prisma/client'; export enum PaymentMethodTypeEnum { TELEBIRR = 'TELEBIRR', CBE_BIRR = 'CBE_BIRR', EBIRR = 'EBIRR', CARD = 'CARD', WALLET = 'WALLET' } @@ -20,3 +21,25 @@ export class AddPaymentMethodDto { @ApiProperty() @IsString() displayName: string; @ApiPropertyOptional() @IsOptional() @IsString() maskedHint?: string; } + +export class ClientActionDto { + @ApiProperty({ enum: ['REDIRECT'] }) type: 'REDIRECT'; + @ApiProperty() url: string; +} + +export class InitiateResponseDto { + @ApiProperty() intentId: string; + @ApiProperty({ enum: PaymentIntentStatus }) status: PaymentIntentStatus; + @ApiPropertyOptional({ type: ClientActionDto }) clientAction?: ClientActionDto; + @ApiPropertyOptional() merchantOrderId?: string; +} + +export class IntentStatusDto { + @ApiProperty() intentId: string; + @ApiProperty({ enum: PaymentIntentStatus }) status: PaymentIntentStatus; + @ApiPropertyOptional({ type: ClientActionDto }) clientAction?: ClientActionDto; + @ApiPropertyOptional() merchantOrderId?: string; + @ApiPropertyOptional() paidAt?: string; + @ApiPropertyOptional() failureCode?: string; + @ApiPropertyOptional() failureMessage?: string; +} diff --git a/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts b/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts new file mode 100644 index 000000000..78ffe2196 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts @@ -0,0 +1,161 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import request from 'supertest'; +import { AppModule } from '../../app.module'; +import { PrismaService } from '../../common/prisma.service'; + +describe('Payments E2E', () => { + let app: INestApplication; + let prisma: PrismaService; + let authToken: string; + let bookingId: string; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true })); + await app.init(); + + prisma = app.get(PrismaService); + + const testUser = await prisma.user.create({ + data: { email: 'payment-test@example.com', phone: '+251911111112', fullName: 'Payment Test User', passwordHash: '$2b$10$abcdefghijklmnopqrstuvwxyz', role: 'PASSENGER' }, + }); + + const passenger = await prisma.passenger.create({ data: { userId: testUser.id } }); + + await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 100000, currency: 'ETB' } }); + + authToken = 'mock-jwt-token'; + + const station1 = await prisma.station.create({ data: { code: 'TST1', name: 'Test Station 1', city: 'Test City', lat: 9.0, lng: 38.0 } }); + const station2 = await prisma.station.create({ data: { code: 'TST2', name: 'Test Station 2', city: 'Test City 2', lat: 9.5, lng: 38.5 } }); + + const train = await prisma.train.create({ data: { number: 'TEST-001', name: 'Test Train' } }); + + const schedule = await prisma.trainSchedule.create({ + data: { trainId: train.id, originStationId: station1.id, destinationStationId: station2.id, departureAt: new Date(Date.now() + 86400000), arrivalAt: new Date(Date.now() + 90000000), durationMinutes: 60 }, + }); + + const seatClass = await prisma.seatClass.upsert({ + where: { name: 'Economy Regular' }, + update: {}, + create: { name: 'Economy Regular', description: 'Standard economy seating', basePrice: 45000, isActive: true }, + }); + + const coach = await prisma.coach.create({ + data: { coachNumber: 'TEST-C1', label: 'A', seatClassId: seatClass.id, mode: 'seat', totalUnits: 10 }, + }); + + await prisma.coachAssignment.create({ data: { scheduleId: schedule.id, coachId: coach.id, positionNumber: 1 } }); + + const seat = await prisma.seat.create({ data: { coachId: coach.id, row: 1, col: 'A', label: '1A', status: 'AVAILABLE' } }); + + const booking = await prisma.booking.create({ + data: { bookingRef: 'TEST-BOOK-001', passengerId: passenger.id, scheduleId: schedule.id, status: 'PENDING_PAYMENT', totalMinor: 50000, currency: 'ETB' }, + }); + + await prisma.bookingSeat.create({ data: { bookingId: booking.id, seatId: seat.id, passengerName: 'Test Passenger' } }); + + bookingId = booking.id; + }); + + afterAll(async () => { + await prisma.$transaction([ + prisma.bookingSeat.deleteMany(), + prisma.paymentIntent.deleteMany(), + prisma.booking.deleteMany(), + prisma.coachAssignment.deleteMany(), + prisma.seat.deleteMany(), + prisma.coach.deleteMany(), + prisma.trainSchedule.deleteMany(), + prisma.train.deleteMany(), + prisma.station.deleteMany({ where: { code: { in: ['TST1', 'TST2'] } } }), + prisma.walletLedgerEntry.deleteMany(), + prisma.walletAccount.deleteMany(), + prisma.passenger.deleteMany(), + prisma.user.deleteMany({ where: { email: 'payment-test@example.com' } }), + ]); + await app.close(); + }); + + describe('POST /payments/initiate', () => { + it('should initiate wallet payment successfully', async () => { + const response = await request(app.getHttpServer()) + .post('/payments/initiate') + .set('Authorization', `Bearer ${authToken}`) + .send({ bookingId, method: 'WALLET' }) + .expect(201); + expect(response.body.intentId).toBeDefined(); + expect(response.body.status).toBe('SUCCEEDED'); + }); + + it('should return 400 for invalid payment method', async () => { + await request(app.getHttpServer()) + .post('/payments/initiate') + .set('Authorization', `Bearer ${authToken}`) + .send({ bookingId, method: 'INVALID_METHOD' }) + .expect(400); + }); + + it('should return 404 for non-existent booking', async () => { + await request(app.getHttpServer()) + .post('/payments/initiate') + .set('Authorization', `Bearer ${authToken}`) + .send({ bookingId: 'non-existent-id', method: 'WALLET' }) + .expect(404); + }); + }); + + describe('GET /payments/intents/:bookingId', () => { + it('should get payment intent status', async () => { + const response = await request(app.getHttpServer()) + .get(`/payments/intents/${bookingId}`) + .set('Authorization', `Bearer ${authToken}`) + .expect(200); + expect(response.body.intentId).toBeDefined(); + expect(response.body.status).toBeDefined(); + }); + + it('should return 404 for non-existent intent', async () => { + await request(app.getHttpServer()) + .get('/payments/intents/non-existent-booking') + .set('Authorization', `Bearer ${authToken}`) + .expect(404); + }); + }); + + describe('Webhook endpoints', () => { + it('should handle Telebirr webhook', async () => { + await request(app.getHttpServer()) + .post('/payments/webhooks/telebirr') + .send({ merch_order_id: 'TEST-ORDER-123', payment_order_id: 'PAY-123', trade_status: 'Completed', sign: 'mock-signature' }) + .expect(200); + }); + + it('should handle CBE Birr webhook', async () => { + await request(app.getHttpServer()) + .post('/payments/webhooks/cbe-birr') + .send({ merchantId: 'TEST-MERCHANT', merchantOrderId: 'TEST-ORDER-123', orderId: 'CBE-ORDER-123', status: 'SUCCESS', signature: 'mock-signature' }) + .expect(200); + }); + + it('should handle eBirr webhook', async () => { + await request(app.getHttpServer()) + .post('/payments/webhooks/ebirr') + .send({ merchantCode: 'TEST-MERCHANT', orderNo: 'TEST-ORDER-123', tradeStatus: 'TRADE_SUCCESS', timestamp: Date.now(), sign: 'mock-signature' }) + .expect(200); + }); + + it('should handle Card webhook', async () => { + await request(app.getHttpServer()) + .post('/payments/webhooks/card') + .set('stripe-signature', 'mock-signature') + .send({ id: 'evt_123', type: 'payment_intent.succeeded', data: { object: { id: 'pi_123', status: 'succeeded', amount: 50000, currency: 'ETB', metadata: { merchantOrderId: 'TEST-ORDER-123', bookingRef: 'TEST-BOOK-001' } } }, created: Math.floor(Date.now() / 1000) }) + .expect(200); + }); + }); +}); diff --git a/apps/edr-passenger-api/src/modules/payments/payments.module.ts b/apps/edr-passenger-api/src/modules/payments/payments.module.ts index 9b2a3d738..387ce5244 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.module.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.module.ts @@ -1,8 +1,32 @@ import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; import { PaymentsController } from './payments.controller'; import { PaymentsService } from './payments.service'; import { SeatsModule } from '../seats/seats.module'; import { TicketsModule } from '../tickets/tickets.module'; +import { TelebirrProvider } from './providers/telebirr.provider'; +import { CbeBirrProvider } from './providers/cbe-birr.provider'; +import { EBirrProvider } from './providers/ebirr.provider'; +import { CardProvider } from './providers/card.provider'; +import { WebhooksController } from './webhooks/webhooks.controller'; +import { TelebirrWebhookService } from './webhooks/telebirr-webhook.service'; +import { CbeBirrWebhookService } from './webhooks/cbe-birr-webhook.service'; +import { EBirrWebhookService } from './webhooks/ebirr-webhook.service'; +import { CardWebhookService } from './webhooks/card-webhook.service'; -@Module({ imports: [SeatsModule, TicketsModule], controllers: [PaymentsController], providers: [PaymentsService] }) +@Module({ + imports: [SeatsModule, TicketsModule, HttpModule.register({ timeout: 10_000 })], + controllers: [PaymentsController, WebhooksController], + providers: [ + PaymentsService, + TelebirrProvider, + CbeBirrProvider, + EBirrProvider, + CardProvider, + TelebirrWebhookService, + CbeBirrWebhookService, + EBirrWebhookService, + CardWebhookService, + ], +}) export class PaymentsModule {} diff --git a/apps/edr-passenger-api/src/modules/payments/payments.service.spec.ts b/apps/edr-passenger-api/src/modules/payments/payments.service.spec.ts new file mode 100644 index 000000000..66775648a --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.spec.ts @@ -0,0 +1,328 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PaymentsService } from './payments.service'; +import { PrismaService } from '../../common/prisma.service'; +import { SeatsService } from '../seats/seats.service'; +import { TicketsService } from '../tickets/tickets.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { TelebirrProvider } from './providers/telebirr.provider'; +import { CbeBirrProvider } from './providers/cbe-birr.provider'; +import { EBirrProvider } from './providers/ebirr.provider'; +import { CardProvider } from './providers/card.provider'; +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; + +describe('PaymentsService', () => { + let service: PaymentsService; + let prisma: PrismaService; + let seatsService: SeatsService; + let ticketsService: TicketsService; + let eventEmitter: EventEmitter2; + + const mockPrisma: Record = { + booking: { + findUnique: jest.fn(), + update: jest.fn(), + }, + paymentIntent: { + findUnique: jest.fn(), + findUniqueOrThrow: jest.fn(), + upsert: jest.fn(), + update: jest.fn(), + create: jest.fn(), + }, + walletAccount: { + findUnique: jest.fn(), + update: jest.fn(), + }, + walletLedgerEntry: { + create: jest.fn(), + }, + loyaltyAccount: { + findUnique: jest.fn(), + update: jest.fn(), + }, + loyaltyLedgerEntry: { + create: jest.fn(), + }, + $transaction: jest.fn((callback: (tx: any) => any) => callback(mockPrisma)), + }; + + const mockSeatsService = { + confirmSeats: jest.fn(), + releaseSeats: jest.fn(), + }; + + const mockTicketsService = { + generate: jest.fn(), + }; + + const mockEventEmitter = { + emit: jest.fn(), + }; + + const mockTelebirrProvider = { + method: PaymentMethodType.TELEBIRR, + initiate: jest.fn(), + queryStatus: jest.fn(), + }; + + const mockCbeBirrProvider = { + method: PaymentMethodType.CBE_BIRR, + initiate: jest.fn(), + queryStatus: jest.fn(), + }; + + const mockEBirrProvider = { + method: PaymentMethodType.EBIRR, + initiate: jest.fn(), + queryStatus: jest.fn(), + }; + + const mockCardProvider = { + method: PaymentMethodType.CARD, + initiate: jest.fn(), + queryStatus: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + PaymentsService, + { provide: PrismaService, useValue: mockPrisma }, + { provide: SeatsService, useValue: mockSeatsService }, + { provide: TicketsService, useValue: mockTicketsService }, + { provide: EventEmitter2, useValue: mockEventEmitter }, + { provide: TelebirrProvider, useValue: mockTelebirrProvider }, + { provide: CbeBirrProvider, useValue: mockCbeBirrProvider }, + { provide: EBirrProvider, useValue: mockEBirrProvider }, + { provide: CardProvider, useValue: mockCardProvider }, + ], + }).compile(); + + service = module.get(PaymentsService); + prisma = module.get(PrismaService); + seatsService = module.get(SeatsService); + ticketsService = module.get(TicketsService); + eventEmitter = module.get(EventEmitter2); + + jest.clearAllMocks(); + }); + + describe('initiatePayment', () => { + const mockBooking = { + id: 'booking-1', + bookingRef: 'EDR123456', + passengerId: 'passenger-1', + totalMinor: 50000, + currency: 'ETB', + status: 'PENDING_PAYMENT', + seats: [{ id: 'seat-1', seatId: 'seat-id-1' }], + }; + + it('should throw NotFoundException if booking not found', async () => { + mockPrisma.booking.findUnique.mockResolvedValue(null); + + await expect( + service.initiatePayment({ + bookingId: 'invalid', + method: 'TELEBIRR' as any, + }), + ).rejects.toThrow(NotFoundException); + }); + + it('should throw BadRequestException if booking not payable', async () => { + mockPrisma.booking.findUnique.mockResolvedValue({ + ...mockBooking, + status: 'CONFIRMED', + }); + + await expect( + service.initiatePayment({ + bookingId: 'booking-1', + method: 'TELEBIRR' as any, + }), + ).rejects.toThrow(BadRequestException); + }); + + it('should initiate Telebirr payment successfully', async () => { + mockPrisma.booking.findUnique.mockResolvedValue(mockBooking); + mockPrisma.paymentIntent.findUnique.mockResolvedValue(null); + mockTelebirrProvider.initiate.mockResolvedValue({ + providerOrderId: 'TB-ORDER-123', + clientAction: { type: 'REDIRECT', url: 'https://telebirr.com/pay' }, + expiresAt: new Date(), + rawInitiation: {}, + }); + mockPrisma.paymentIntent.upsert.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.REQUIRES_ACTION, + merchantOrderId: 'MERCH-123', + clientAction: { type: 'REDIRECT', url: 'https://telebirr.com/pay' }, + }); + + const result = await service.initiatePayment({ + bookingId: 'booking-1', + method: 'TELEBIRR' as any, + }); + + expect(result.status).toBe(PaymentIntentStatus.REQUIRES_ACTION); + expect(mockTelebirrProvider.initiate).toHaveBeenCalled(); + }); + + it('should initiate CBE Birr payment successfully', async () => { + mockPrisma.booking.findUnique.mockResolvedValue(mockBooking); + mockPrisma.paymentIntent.findUnique.mockResolvedValue(null); + mockCbeBirrProvider.initiate.mockResolvedValue({ + providerOrderId: 'CBE-ORDER-123', + clientAction: { type: 'REDIRECT', url: 'https://cbe.com/pay' }, + expiresAt: new Date(), + rawInitiation: {}, + }); + mockPrisma.paymentIntent.upsert.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.REQUIRES_ACTION, + merchantOrderId: 'MERCH-123', + clientAction: { type: 'REDIRECT', url: 'https://cbe.com/pay' }, + }); + + const result = await service.initiatePayment({ + bookingId: 'booking-1', + method: 'CBE_BIRR' as any, + }); + + expect(result.status).toBe(PaymentIntentStatus.REQUIRES_ACTION); + expect(mockCbeBirrProvider.initiate).toHaveBeenCalled(); + }); + + it('should initiate wallet payment and debit successfully', async () => { + mockPrisma.booking.findUnique.mockResolvedValue(mockBooking); + mockPrisma.paymentIntent.findUnique.mockResolvedValue(null); + mockPrisma.walletAccount.findUnique.mockResolvedValue({ + id: 'wallet-1', + passengerId: 'passenger-1', + balanceMinor: 100000, + }); + mockPrisma.paymentIntent.upsert.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.PROCESSING, + }); + mockPrisma.paymentIntent.findUniqueOrThrow.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.SUCCEEDED, + bookingId: 'booking-1', + }); + mockPrisma.loyaltyAccount.findUnique.mockResolvedValue({ + id: 'loyalty-1', + pointsBalance: 100, + }); + + const result = await service.initiatePayment({ + bookingId: 'booking-1', + method: 'WALLET' as any, + }); + + expect(result.status).toBe(PaymentIntentStatus.SUCCEEDED); + expect(mockSeatsService.confirmSeats).toHaveBeenCalled(); + expect(mockTicketsService.generate).toHaveBeenCalled(); + }); + + it('should fail wallet payment with insufficient balance', async () => { + mockPrisma.booking.findUnique.mockResolvedValue(mockBooking); + mockPrisma.paymentIntent.findUnique.mockResolvedValue(null); + mockPrisma.walletAccount.findUnique.mockResolvedValue({ + id: 'wallet-1', + passengerId: 'passenger-1', + balanceMinor: 10000, // Less than booking total + }); + mockPrisma.paymentIntent.upsert.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.FAILED, + failureCode: 'INSUFFICIENT_BALANCE', + }); + + const result = await service.initiatePayment({ + bookingId: 'booking-1', + method: 'WALLET' as any, + }); + + expect(result.status).toBe(PaymentIntentStatus.FAILED); + }); + }); + + describe('finalizePaymentSuccess', () => { + it('should finalize payment and issue ticket', async () => { + const mockIntent = { + id: 'intent-1', + bookingId: 'booking-1', + status: PaymentIntentStatus.PROCESSING, + }; + const mockBooking = { + id: 'booking-1', + passengerId: 'passenger-1', + totalMinor: 50000, + seats: [{ seatId: 'seat-1' }], + }; + + mockPrisma.paymentIntent.findUnique.mockResolvedValue(mockIntent); + mockPrisma.booking.findUnique.mockResolvedValue(mockBooking); + mockPrisma.loyaltyAccount.findUnique.mockResolvedValue({ + id: 'loyalty-1', + pointsBalance: 100, + }); + + const result = await service.finalizePaymentSuccess({ + intentId: 'intent-1', + providerTxnId: 'TXN-123', + }); + + expect(result.alreadyFinalized).toBe(false); + expect(mockSeatsService.confirmSeats).toHaveBeenCalledWith(['seat-1']); + expect(mockTicketsService.generate).toHaveBeenCalledWith('booking-1'); + expect(mockEventEmitter.emit).toHaveBeenCalledWith('payment.succeeded', { + booking: mockBooking, + }); + }); + + it('should return alreadyFinalized if payment already succeeded', async () => { + mockPrisma.paymentIntent.findUnique.mockResolvedValue({ + id: 'intent-1', + status: PaymentIntentStatus.SUCCEEDED, + }); + + const result = await service.finalizePaymentSuccess({ + intentId: 'intent-1', + }); + + expect(result.alreadyFinalized).toBe(true); + }); + }); + + describe('getIntentByBookingId', () => { + it('should return intent status', async () => { + const mockIntent = { + id: 'intent-1', + bookingId: 'booking-1', + status: PaymentIntentStatus.SUCCEEDED, + method: PaymentMethodType.TELEBIRR, + paidAt: new Date(), + merchantOrderId: 'MERCH-123', + updatedAt: new Date(), + }; + + mockPrisma.paymentIntent.findUnique.mockResolvedValue(mockIntent); + + const result = await service.getIntentByBookingId('booking-1'); + + expect(result.intentId).toBe('intent-1'); + expect(result.status).toBe(PaymentIntentStatus.SUCCEEDED); + }); + + it('should throw NotFoundException if intent not found', async () => { + mockPrisma.paymentIntent.findUnique.mockResolvedValue(null); + + await expect(service.getIntentByBookingId('invalid')).rejects.toThrow( + NotFoundException, + ); + }); + }); +}); diff --git a/apps/edr-passenger-api/src/modules/payments/payments.service.ts b/apps/edr-passenger-api/src/modules/payments/payments.service.ts index 8f1df1c7b..9f1ae5341 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -1,56 +1,268 @@ -import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; import { SeatsService } from '../seats/seats.service'; import { TicketsService } from '../tickets/tickets.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { InitiatePaymentDto, RefundDto, AddPaymentMethodDto } from './payments.dto'; -import { telebirrAdapter, cbeBirrAdapter, eBirrAdapter, cardAdapter } from './payments.adapters'; +import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { InitiatePaymentDto, RefundDto, AddPaymentMethodDto, InitiateResponseDto, IntentStatusDto } from './payments.dto'; +import { PaymentProvider, ProviderStatus } from './payments.types'; +import { TelebirrProvider } from './providers/telebirr.provider'; +import { CbeBirrProvider } from './providers/cbe-birr.provider'; +import { EBirrProvider } from './providers/ebirr.provider'; +import { CardProvider } from './providers/card.provider'; +import { createMerchantOrderId } from './providers/telebirr.crypto'; + +const NON_TERMINAL_STATUSES: PaymentIntentStatus[] = [ + PaymentIntentStatus.REQUIRES_ACTION, + PaymentIntentStatus.PROCESSING, + PaymentIntentStatus.SUCCEEDED, +]; @Injectable() export class PaymentsService { + private readonly logger = new Logger(PaymentsService.name); + private readonly providers: Map; + constructor( private prisma: PrismaService, private seatsService: SeatsService, private ticketsService: TicketsService, private eventEmitter: EventEmitter2, - ) {} + private telebirrProvider: TelebirrProvider, + private cbeBirrProvider: CbeBirrProvider, + private eBirrProvider: EBirrProvider, + private cardProvider: CardProvider, + ) { + this.providers = new Map([ + [PaymentMethodType.TELEBIRR, this.telebirrProvider], + [PaymentMethodType.CBE_BIRR, this.cbeBirrProvider], + [PaymentMethodType.EBIRR, this.eBirrProvider], + [PaymentMethodType.CARD, this.cardProvider], + ]); + } - async initiatePayment(dto: InitiatePaymentDto) { - const booking = await this.prisma.booking.findUnique({ where: { id: dto.bookingId }, include: { seats: true } }); + async initiatePayment(dto: InitiatePaymentDto): Promise { + const booking = await this.prisma.booking.findUnique({ + where: { id: dto.bookingId }, + include: { seats: true }, + }); if (!booking) throw new NotFoundException('Booking not found'); - if (booking.status !== 'PENDING_PAYMENT') throw new BadRequestException('Booking not payable'); - - let result; - if (dto.method === 'WALLET') { - result = await this.prisma.$transaction(async (tx) => { - const wallet = await tx.walletAccount.findUnique({ where: { passengerId: booking.passengerId } }); - if (!wallet || wallet.balanceMinor < booking.totalMinor) return { success: false, providerRef: '' }; - const newBalance = wallet.balanceMinor - booking.totalMinor; - await tx.walletAccount.update({ where: { passengerId: booking.passengerId }, data: { balanceMinor: newBalance } }); - await tx.walletLedgerEntry.create({ data: { walletId: wallet.id, type: 'DEBIT', amountMinor: booking.totalMinor, balanceAfterMinor: newBalance, description: `Train Ticket - ${booking.bookingRef}`, relatedBookingId: booking.id } }); - return { success: true, providerRef: `WALLET-${Date.now()}` }; - }); - } else { - const adapters = { TELEBIRR: telebirrAdapter, CBE_BIRR: cbeBirrAdapter, EBIRR: eBirrAdapter, CARD: cardAdapter } as any; - result = await adapters[dto.method](booking.totalMinor, booking.bookingRef); + if (booking.status !== 'PENDING_PAYMENT') { + throw new BadRequestException('Booking not payable'); } - const status = result.success ? 'SUCCEEDED' : 'FAILED'; - const intent = await this.prisma.paymentIntent.upsert({ + const existing = await this.prisma.paymentIntent.findUnique({ where: { bookingId: dto.bookingId }, - update: { status, providerRef: result.providerRef, clientAction: result.clientAction as any }, - create: { bookingId: dto.bookingId, amountMinor: booking.totalMinor, method: dto.method as any, status: status as any, providerRef: result.providerRef, clientAction: result.clientAction as any }, + }); + if (existing && NON_TERMINAL_STATUSES.includes(existing.status)) { + return this.formatIntentResponse(existing); + } + + const method = dto.method as PaymentMethodType; + + if (method === PaymentMethodType.WALLET) { + return this.initiateWalletPayment(booking); + } + + const provider = this.providers.get(method); + if (provider) { + return this.initiateProviderPayment(booking, provider); + } + + throw new BadRequestException(`Unsupported payment method: ${method}`); + } + + private async initiateWalletPayment( + booking: Prisma.BookingGetPayload<{ include: { seats: true } }>, + ): Promise { + const debitResult = await this.prisma.$transaction(async (tx) => { + const wallet = await tx.walletAccount.findUnique({ + where: { passengerId: booking.passengerId }, + }); + if (!wallet || wallet.balanceMinor < booking.totalMinor) { + return { success: false }; + } + const newBalance = wallet.balanceMinor - booking.totalMinor; + await tx.walletAccount.update({ + where: { passengerId: booking.passengerId }, + data: { balanceMinor: newBalance }, + }); + await tx.walletLedgerEntry.create({ + data: { + walletId: wallet.id, + type: 'DEBIT', + amountMinor: booking.totalMinor, + balanceAfterMinor: newBalance, + description: `Train Ticket - ${booking.bookingRef}`, + relatedBookingId: booking.id, + }, + }); + return { success: true }; }); - if (result.success) { - await this.seatsService.confirmSeats(booking.seats.map((s) => s.seatId)); - await this.prisma.booking.update({ where: { id: dto.bookingId }, data: { status: 'CONFIRMED' } }); - await this.ticketsService.generate(dto.bookingId); - await this.awardLoyaltyPoints(booking.passengerId, booking.totalMinor, booking.id); - this.eventEmitter.emit('payment.succeeded', { booking }); + if (!debitResult.success) { + const failed = await this.prisma.paymentIntent.upsert({ + where: { bookingId: booking.id }, + update: { + status: PaymentIntentStatus.FAILED, + failureCode: 'INSUFFICIENT_BALANCE', + }, + create: { + bookingId: booking.id, + amountMinor: booking.totalMinor, + method: PaymentMethodType.WALLET, + status: PaymentIntentStatus.FAILED, + failureCode: 'INSUFFICIENT_BALANCE', + }, + }); + return this.formatIntentResponse(failed); } - return { id: intent.id, status: result.success ? 'SUCCESS' : 'FAILED', success: result.success }; + const intent = await this.prisma.paymentIntent.upsert({ + where: { bookingId: booking.id }, + update: { status: PaymentIntentStatus.PROCESSING }, + create: { + bookingId: booking.id, + amountMinor: booking.totalMinor, + method: PaymentMethodType.WALLET, + status: PaymentIntentStatus.PROCESSING, + providerRef: `WALLET-${Date.now()}`, + }, + }); + await this.finalizePaymentSuccess({ intentId: intent.id }); + const refreshed = await this.prisma.paymentIntent.findUniqueOrThrow({ + where: { id: intent.id }, + }); + return this.formatIntentResponse(refreshed); + } + + private async initiateProviderPayment( + booking: Prisma.BookingGetPayload<{ include: { seats: true } }>, + provider: PaymentProvider, + ): Promise { + const merchantOrderId = createMerchantOrderId(); + const result = await provider.initiate({ + merchantOrderId, + bookingRef: booking.bookingRef, + amountMinor: booking.totalMinor, + currency: booking.currency, + }); + + const intent = await this.prisma.paymentIntent.upsert({ + where: { bookingId: booking.id }, + update: { + status: PaymentIntentStatus.REQUIRES_ACTION, + method: provider.method, + merchantOrderId, + providerOrderId: result.providerOrderId, + clientAction: result.clientAction as unknown as Prisma.InputJsonValue, + rawInitiation: result.rawInitiation as Prisma.InputJsonValue, + expiresAt: result.expiresAt, + failureCode: null, + failureMessage: null, + }, + create: { + bookingId: booking.id, + amountMinor: booking.totalMinor, + currency: booking.currency, + method: provider.method, + status: PaymentIntentStatus.REQUIRES_ACTION, + merchantOrderId, + providerOrderId: result.providerOrderId, + clientAction: result.clientAction as unknown as Prisma.InputJsonValue, + rawInitiation: result.rawInitiation as Prisma.InputJsonValue, + expiresAt: result.expiresAt, + }, + }); + return this.formatIntentResponse(intent); + } + + + + private formatIntentResponse( + intent: Prisma.PaymentIntentGetPayload>, + ): InitiateResponseDto { + const clientAction = + intent.clientAction && typeof intent.clientAction === 'object' + ? (intent.clientAction as unknown as { type: 'REDIRECT'; url: string }) + : undefined; + return { + intentId: intent.id, + status: intent.status, + clientAction, + merchantOrderId: intent.merchantOrderId ?? undefined, + }; + } + + async getIntentByBookingId(bookingId: string): Promise { + const intent = await this.prisma.paymentIntent.findUnique({ + where: { bookingId }, + }); + if (!intent) throw new NotFoundException('PaymentIntent not found'); + + const refreshable = + intent.status === PaymentIntentStatus.REQUIRES_ACTION || + intent.status === PaymentIntentStatus.PROCESSING; + const stale = intent.updatedAt.getTime() < Date.now() - 5_000; + const provider = this.providers.get(intent.method); + + if (refreshable && stale && intent.merchantOrderId && provider) { + try { + const status = await provider.queryStatus(intent.merchantOrderId); + await this.applyProviderStatus(intent.id, status); + const refreshed = await this.prisma.paymentIntent.findUniqueOrThrow({ + where: { id: intent.id }, + }); + return this.formatIntentStatus(refreshed); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.warn( + `queryStatus failed for intent ${intent.id}: ${message}; returning cached`, + ); + } + } + + return this.formatIntentStatus(intent); + } + + private async applyProviderStatus( + intentId: string, + status: ProviderStatus, + ): Promise { + if (status.status === PaymentIntentStatus.SUCCEEDED) { + await this.finalizePaymentSuccess({ + intentId, + providerTxnId: status.providerTxnId, + }); + return; + } + if (status.status === PaymentIntentStatus.FAILED) { + await this.markPaymentFailed({ + intentId, + failureCode: status.failureCode, + failureMessage: status.failureMessage, + }); + return; + } + await this.prisma.paymentIntent.update({ + where: { id: intentId }, + data: { + status: status.status, + providerTxnId: status.providerTxnId ?? undefined, + }, + }); + } + + private formatIntentStatus( + intent: Prisma.PaymentIntentGetPayload>, + ): IntentStatusDto { + const base = this.formatIntentResponse(intent); + return { + ...base, + paidAt: intent.paidAt?.toISOString(), + failureCode: intent.failureCode ?? undefined, + failureMessage: intent.failureMessage ?? undefined, + }; } async refund(dto: RefundDto) { @@ -69,6 +281,76 @@ export class PaymentsService { getPaymentMethods(userId: string) { return this.prisma.paymentMethod.findMany({ where: { userId }, orderBy: { isDefault: 'desc' } }); } + async finalizePaymentSuccess(input: { + intentId: string; + providerTxnId?: string; + paidAt?: Date; + }): Promise<{ alreadyFinalized: boolean }> { + const intent = await this.prisma.paymentIntent.findUnique({ + where: { id: input.intentId }, + }); + if (!intent) throw new NotFoundException('PaymentIntent not found'); + if (intent.status === PaymentIntentStatus.SUCCEEDED) { + return { alreadyFinalized: true }; + } + if (intent.status === PaymentIntentStatus.CANCELLED) { + throw new BadRequestException('PaymentIntent is cancelled; cannot finalize'); + } + + const booking = await this.prisma.booking.findUnique({ + where: { id: intent.bookingId }, + include: { seats: true }, + }); + if (!booking) throw new NotFoundException('Booking not found'); + + const paidAt = input.paidAt ?? new Date(); + await this.prisma.$transaction(async (tx) => { + await tx.paymentIntent.update({ + where: { id: intent.id }, + data: { + status: PaymentIntentStatus.SUCCEEDED, + providerTxnId: input.providerTxnId ?? intent.providerTxnId ?? undefined, + paidAt, + }, + }); + await tx.booking.update({ + where: { id: booking.id }, + data: { status: 'CONFIRMED' }, + }); + }); + + await this.seatsService.confirmSeats(booking.seats.map((s) => s.seatId)); + await this.ticketsService.generate(booking.id); + await this.awardLoyaltyPoints(booking.passengerId, booking.totalMinor, booking.id); + this.eventEmitter.emit('payment.succeeded', { booking }); + return { alreadyFinalized: false }; + } + + async markPaymentFailed(input: { + intentId: string; + failureCode?: string; + failureMessage?: string; + }): Promise { + const intent = await this.prisma.paymentIntent.findUnique({ + where: { id: input.intentId }, + }); + if (!intent) throw new NotFoundException('PaymentIntent not found'); + if ( + intent.status === PaymentIntentStatus.SUCCEEDED || + intent.status === PaymentIntentStatus.CANCELLED + ) { + return; + } + await this.prisma.paymentIntent.update({ + where: { id: intent.id }, + data: { + status: PaymentIntentStatus.FAILED, + failureCode: input.failureCode, + failureMessage: input.failureMessage, + }, + }); + } + private async awardLoyaltyPoints(passengerId: string, amountMinor: number, bookingId: string) { const points = Math.floor(amountMinor / 100); const account = await this.prisma.loyaltyAccount.findUnique({ where: { passengerId } }); diff --git a/apps/edr-passenger-api/src/modules/payments/payments.types.ts b/apps/edr-passenger-api/src/modules/payments/payments.types.ts new file mode 100644 index 000000000..336f4b511 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/payments.types.ts @@ -0,0 +1,34 @@ +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; + +export interface ClientAction { + type: 'REDIRECT'; + url: string; +} + +export interface ProviderInitiationInput { + merchantOrderId: string; + bookingRef: string; + amountMinor: number; + currency: string; +} + +export interface ProviderInitiationResult { + providerOrderId: string; + clientAction: ClientAction; + expiresAt: Date; + rawInitiation: Record; +} + +export interface ProviderStatus { + status: PaymentIntentStatus; + providerTxnId?: string; + failureCode?: string; + failureMessage?: string; + rawResponse: Record; +} + +export interface PaymentProvider { + readonly method: PaymentMethodType; + initiate(input: ProviderInitiationInput): Promise; + queryStatus(merchantOrderId: string): Promise; +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/card.provider.ts b/apps/edr-passenger-api/src/modules/payments/providers/card.provider.ts new file mode 100644 index 000000000..6f444945b --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/card.provider.ts @@ -0,0 +1,218 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { AxiosError, AxiosRequestConfig } from 'axios'; +import { firstValueFrom } from 'rxjs'; +import * as crypto from 'node:crypto'; +import { + PaymentProvider, + ProviderInitiationInput, + ProviderInitiationResult, + ProviderStatus, +} from '../payments.types'; + +interface CardInitiateRequest { + amount: number; + currency: string; + description: string; + metadata: { + merchantOrderId: string; + bookingRef: string; + }; + return_url: string; + webhook_url: string; +} + +interface CardInitiateResponse { + id: string; + status: string; + client_secret: string; + checkout_url: string; + expires_at: number; +} + +interface CardQueryResponse { + id: string; + status: string; + amount: number; + currency: string; + transaction_id?: string; + paid_at?: number; + failure_code?: string; + failure_message?: string; +} + +@Injectable() +export class CardProvider implements PaymentProvider { + readonly method = PaymentMethodType.CARD; + private readonly logger = new Logger(CardProvider.name); + + constructor( + private readonly config: ConfigService, + private readonly http: HttpService, + ) {} + + async initiate(input: ProviderInitiationInput): Promise { + const amount = input.amountMinor / 100; + + const requestBody: CardInitiateRequest = { + amount, + currency: input.currency, + description: `EDR Train Booking ${input.bookingRef}`, + metadata: { + merchantOrderId: input.merchantOrderId, + bookingRef: input.bookingRef, + }, + return_url: this.returnUrl, + webhook_url: this.webhookUrl, + }; + + const response = await this.postJson( + `${this.baseUrl}/v1/payment_intents`, + requestBody, + ); + + if (!response.id) { + throw new Error(`Card gateway initiate failed: ${JSON.stringify(response)}`); + } + + const expiresAt = new Date(response.expires_at * 1000); + + return { + providerOrderId: response.id, + clientAction: { type: 'REDIRECT', url: response.checkout_url }, + expiresAt, + rawInitiation: { + request: requestBody, + response, + }, + }; + } + + async queryStatus(merchantOrderId: string): Promise { + // For card payments, we need to find the payment intent by metadata + // In a real implementation, we'd store the provider order ID and use it directly + const response = await this.getJson( + `${this.baseUrl}/v1/payment_intents/search?metadata[merchantOrderId]=${merchantOrderId}`, + ); + + const mapped = this.mapStatus(response.status); + + return { + status: mapped, + providerTxnId: response.transaction_id, + failureCode: response.failure_code, + failureMessage: response.failure_message, + rawResponse: response as unknown as Record, + }; + } + + verifyWebhookSignature(payload: Record, signature: string): boolean { + const payloadString = JSON.stringify(payload); + const expectedSignature = crypto + .createHmac('sha256', this.webhookSecret) + .update(payloadString) + .digest('hex'); + + try { + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature), + ); + } catch { + return false; + } + } + + mapWebhookStatus(status: string): PaymentIntentStatus { + return this.mapStatus(status); + } + + private mapStatus(status: string): PaymentIntentStatus { + switch (status?.toLowerCase()) { + case 'succeeded': + case 'paid': + return PaymentIntentStatus.SUCCEEDED; + case 'failed': + case 'canceled': + case 'expired': + return PaymentIntentStatus.FAILED; + case 'requires_payment_method': + case 'requires_confirmation': + case 'requires_action': + return PaymentIntentStatus.REQUIRES_ACTION; + case 'processing': + return PaymentIntentStatus.PROCESSING; + default: + return PaymentIntentStatus.PROCESSING; + } + } + + private async postJson(url: string, body: unknown): Promise { + const config: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}`, + }, + timeout: 10_000, + }; + + const started = Date.now(); + try { + const res = await firstValueFrom(this.http.post(url, body, config)); + this.logger.debug(`Card Gateway POST ${url} status=${res.status} latency=${Date.now() - started}ms`); + return res.data; + } catch (err) { + if (err instanceof AxiosError) { + this.logger.error( + `Card Gateway POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`, + ); + } else { + this.logger.error(`Card Gateway POST ${url} threw: ${err instanceof Error ? err.message : err}`); + } + throw err; + } + } + + private async getJson(url: string): Promise { + const config: AxiosRequestConfig = { + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + }, + timeout: 10_000, + }; + + const started = Date.now(); + try { + const res = await firstValueFrom(this.http.get(url, config)); + this.logger.debug(`Card Gateway GET ${url} status=${res.status} latency=${Date.now() - started}ms`); + return res.data; + } catch (err) { + if (err instanceof AxiosError) { + this.logger.error( + `Card Gateway GET ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`, + ); + } else { + this.logger.error(`Card Gateway GET ${url} threw: ${err instanceof Error ? err.message : err}`); + } + throw err; + } + } + + private get baseUrl(): string { + return this.config.get('card.baseUrl') ?? ''; + } + private get apiKey(): string { + return this.config.get('card.apiKey') ?? ''; + } + private get webhookSecret(): string { + return this.config.get('card.webhookSecret') ?? ''; + } + private get webhookUrl(): string { + return this.config.get('card.webhookUrl') ?? ''; + } + private get returnUrl(): string { + return this.config.get('card.returnUrl') ?? ''; + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/cbe-birr.provider.ts b/apps/edr-passenger-api/src/modules/payments/providers/cbe-birr.provider.ts new file mode 100644 index 000000000..9f94a235a --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/cbe-birr.provider.ts @@ -0,0 +1,215 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { AxiosError, AxiosRequestConfig } from 'axios'; +import { firstValueFrom } from 'rxjs'; +import * as crypto from 'node:crypto'; +import { + PaymentProvider, + ProviderInitiationInput, + ProviderInitiationResult, + ProviderStatus, +} from '../payments.types'; + +interface CbeBirrInitiateRequest { + merchantId: string; + merchantOrderId: string; + amount: string; + currency: string; + description: string; + returnUrl: string; + notifyUrl: string; + timestamp: string; + signature: string; +} + +interface CbeBirrInitiateResponse { + success: boolean; + orderId: string; + paymentUrl: string; + expiresIn: number; +} + +interface CbeBirrQueryResponse { + success: boolean; + orderId: string; + status: string; + transactionId?: string; + amount?: string; + paidAt?: string; +} + +@Injectable() +export class CbeBirrProvider implements PaymentProvider { + readonly method = PaymentMethodType.CBE_BIRR; + private readonly logger = new Logger(CbeBirrProvider.name); + + constructor( + private readonly config: ConfigService, + private readonly http: HttpService, + ) {} + + async initiate(input: ProviderInitiationInput): Promise { + const amount = (input.amountMinor / 100).toFixed(2); + const timestamp = new Date().toISOString(); + + const requestBody: CbeBirrInitiateRequest = { + merchantId: this.merchantId, + merchantOrderId: input.merchantOrderId, + amount, + currency: input.currency, + description: `EDR Booking ${input.bookingRef}`, + returnUrl: this.returnUrl, + notifyUrl: this.notifyUrl, + timestamp, + signature: this.signRequest({ + merchantId: this.merchantId, + merchantOrderId: input.merchantOrderId, + amount, + timestamp, + }), + }; + + const response = await this.postJson( + `${this.baseUrl}/api/v1/payment/initiate`, + requestBody, + ); + + if (!response.success || !response.orderId) { + throw new Error(`CBE Birr initiate failed: ${JSON.stringify(response)}`); + } + + const expiresAt = new Date(Date.now() + response.expiresIn * 1000); + + return { + providerOrderId: response.orderId, + clientAction: { type: 'REDIRECT', url: response.paymentUrl }, + expiresAt, + rawInitiation: { + request: this.sanitize(requestBody), + response, + }, + }; + } + + async queryStatus(merchantOrderId: string): Promise { + const timestamp = new Date().toISOString(); + const signature = this.signRequest({ + merchantId: this.merchantId, + merchantOrderId, + timestamp, + }); + + const response = await this.postJson( + `${this.baseUrl}/api/v1/payment/query`, + { + merchantId: this.merchantId, + merchantOrderId, + timestamp, + signature, + }, + ); + + const mapped = this.mapStatus(response.status); + + return { + status: mapped, + providerTxnId: response.transactionId, + failureCode: mapped === PaymentIntentStatus.FAILED ? response.status : undefined, + rawResponse: response as unknown as Record, + }; + } + + verifyWebhookSignature(payload: Record): boolean { + const { signature, ...data } = payload; + if (!signature || typeof signature !== 'string') return false; + + const expectedSignature = this.signRequest(data); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature), + ); + } + + mapWebhookStatus(status: string): PaymentIntentStatus { + return this.mapStatus(status); + } + + private mapStatus(status: string): PaymentIntentStatus { + switch (status?.toUpperCase()) { + case 'SUCCESS': + case 'COMPLETED': + return PaymentIntentStatus.SUCCEEDED; + case 'FAILED': + case 'REJECTED': + case 'EXPIRED': + return PaymentIntentStatus.FAILED; + case 'PENDING': + return PaymentIntentStatus.REQUIRES_ACTION; + case 'PROCESSING': + return PaymentIntentStatus.PROCESSING; + default: + return PaymentIntentStatus.PROCESSING; + } + } + + private signRequest(data: Record): string { + const sortedKeys = Object.keys(data).sort(); + const signString = sortedKeys + .map((key) => `${key}=${data[key]}`) + .join('&'); + + return crypto + .createHmac('sha256', this.secretKey) + .update(signString) + .digest('hex'); + } + + private async postJson(url: string, body: unknown): Promise { + const config: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/json', + 'X-Merchant-Id': this.merchantId, + }, + timeout: 10_000, + }; + + const started = Date.now(); + try { + const res = await firstValueFrom(this.http.post(url, body, config)); + this.logger.debug(`CBE Birr POST ${url} status=${res.status} latency=${Date.now() - started}ms`); + return res.data; + } catch (err) { + if (err instanceof AxiosError) { + this.logger.error( + `CBE Birr POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`, + ); + } else { + this.logger.error(`CBE Birr POST ${url} threw: ${err instanceof Error ? err.message : err}`); + } + throw err; + } + } + + private sanitize(body: CbeBirrInitiateRequest): Record { + const { signature: _signature, ...rest } = body; + return rest; + } + + private get baseUrl(): string { + return this.config.get('cbe.baseUrl') ?? ''; + } + private get merchantId(): string { + return this.config.get('cbe.merchantId') ?? ''; + } + private get secretKey(): string { + return this.config.get('cbe.secretKey') ?? ''; + } + private get notifyUrl(): string { + return this.config.get('cbe.notifyUrl') ?? ''; + } + private get returnUrl(): string { + return this.config.get('cbe.returnUrl') ?? ''; + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/ebirr.provider.ts b/apps/edr-passenger-api/src/modules/payments/providers/ebirr.provider.ts new file mode 100644 index 000000000..701d4b5fb --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/ebirr.provider.ts @@ -0,0 +1,228 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { AxiosError, AxiosRequestConfig } from 'axios'; +import { firstValueFrom } from 'rxjs'; +import * as crypto from 'node:crypto'; +import { + PaymentProvider, + ProviderInitiationInput, + ProviderInitiationResult, + ProviderStatus, +} from '../payments.types'; + +interface EBirrInitiateRequest { + merchantCode: string; + orderNo: string; + amount: number; + currency: string; + subject: string; + body: string; + notifyUrl: string; + returnUrl: string; + timestamp: number; + sign: string; +} + +interface EBirrInitiateResponse { + code: string; + message: string; + data?: { + orderNo: string; + payUrl: string; + expireTime: number; + }; +} + +interface EBirrQueryResponse { + code: string; + message: string; + data?: { + orderNo: string; + tradeStatus: string; + tradeNo?: string; + totalAmount?: number; + payTime?: number; + }; +} + +@Injectable() +export class EBirrProvider implements PaymentProvider { + readonly method = PaymentMethodType.EBIRR; + private readonly logger = new Logger(EBirrProvider.name); + + constructor( + private readonly config: ConfigService, + private readonly http: HttpService, + ) {} + + async initiate(input: ProviderInitiationInput): Promise { + const amount = input.amountMinor / 100; + const timestamp = Date.now(); + + const requestBody: EBirrInitiateRequest = { + merchantCode: this.merchantCode, + orderNo: input.merchantOrderId, + amount, + currency: input.currency, + subject: `EDR Ticket`, + body: `Train booking ${input.bookingRef}`, + notifyUrl: this.notifyUrl, + returnUrl: this.returnUrl, + timestamp, + sign: this.signRequest({ + merchantCode: this.merchantCode, + orderNo: input.merchantOrderId, + amount, + timestamp, + }), + }; + + const response = await this.postJson( + `${this.baseUrl}/gateway/api/pay/create`, + requestBody, + ); + + if (response.code !== '0000' || !response.data?.orderNo) { + throw new Error(`eBirr initiate failed: ${response.message}`); + } + + const expiresAt = new Date(response.data.expireTime); + + return { + providerOrderId: response.data.orderNo, + clientAction: { type: 'REDIRECT', url: response.data.payUrl }, + expiresAt, + rawInitiation: { + request: this.sanitize(requestBody), + response, + }, + }; + } + + async queryStatus(merchantOrderId: string): Promise { + const timestamp = Date.now(); + const requestBody = { + merchantCode: this.merchantCode, + orderNo: merchantOrderId, + timestamp, + sign: this.signRequest({ + merchantCode: this.merchantCode, + orderNo: merchantOrderId, + timestamp, + }), + }; + + const response = await this.postJson( + `${this.baseUrl}/gateway/api/pay/query`, + requestBody, + ); + + if (response.code !== '0000' || !response.data) { + throw new Error(`eBirr query failed: ${response.message}`); + } + + const mapped = this.mapStatus(response.data.tradeStatus); + + return { + status: mapped, + providerTxnId: response.data.tradeNo, + failureCode: mapped === PaymentIntentStatus.FAILED ? response.data.tradeStatus : undefined, + rawResponse: response as unknown as Record, + }; + } + + verifyWebhookSignature(payload: Record): boolean { + const { sign, ...data } = payload; + if (!sign || typeof sign !== 'string') return false; + + const expectedSign = this.signRequest(data); + return crypto.timingSafeEqual( + Buffer.from(sign), + Buffer.from(expectedSign), + ); + } + + mapWebhookStatus(tradeStatus: string): PaymentIntentStatus { + return this.mapStatus(tradeStatus); + } + + private mapStatus(tradeStatus: string): PaymentIntentStatus { + switch (tradeStatus?.toUpperCase()) { + case 'TRADE_SUCCESS': + case 'SUCCESS': + return PaymentIntentStatus.SUCCEEDED; + case 'TRADE_CLOSED': + case 'TRADE_FAILED': + case 'FAILED': + return PaymentIntentStatus.FAILED; + case 'WAIT_BUYER_PAY': + case 'PENDING': + return PaymentIntentStatus.REQUIRES_ACTION; + case 'PROCESSING': + return PaymentIntentStatus.PROCESSING; + default: + return PaymentIntentStatus.PROCESSING; + } + } + + private signRequest(data: Record): string { + const sortedKeys = Object.keys(data).sort(); + const signString = sortedKeys + .map((key) => `${key}=${data[key]}`) + .join('&') + `&key=${this.secretKey}`; + + return crypto + .createHash('md5') + .update(signString) + .digest('hex') + .toUpperCase(); + } + + private async postJson(url: string, body: unknown): Promise { + const config: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/json', + }, + timeout: 10_000, + }; + + const started = Date.now(); + try { + const res = await firstValueFrom(this.http.post(url, body, config)); + this.logger.debug(`eBirr POST ${url} status=${res.status} latency=${Date.now() - started}ms`); + return res.data; + } catch (err) { + if (err instanceof AxiosError) { + this.logger.error( + `eBirr POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`, + ); + } else { + this.logger.error(`eBirr POST ${url} threw: ${err instanceof Error ? err.message : err}`); + } + throw err; + } + } + + private sanitize(body: EBirrInitiateRequest): Record { + const { sign: _sign, ...rest } = body; + return rest; + } + + private get baseUrl(): string { + return this.config.get('ebirr.baseUrl') ?? ''; + } + private get merchantCode(): string { + return this.config.get('ebirr.merchantCode') ?? ''; + } + private get secretKey(): string { + return this.config.get('ebirr.secretKey') ?? ''; + } + private get notifyUrl(): string { + return this.config.get('ebirr.notifyUrl') ?? ''; + } + private get returnUrl(): string { + return this.config.get('ebirr.returnUrl') ?? ''; + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/payment-provider.interface.ts b/apps/edr-passenger-api/src/modules/payments/providers/payment-provider.interface.ts new file mode 100644 index 000000000..a571f28be --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/payment-provider.interface.ts @@ -0,0 +1,9 @@ +export type { + PaymentProvider, + ProviderInitiationInput, + ProviderInitiationResult, + ProviderStatus, + ClientAction, +} from '../payments.types'; + +export const PAYMENT_PROVIDERS = Symbol('PAYMENT_PROVIDERS'); diff --git a/apps/edr-passenger-api/src/modules/payments/providers/telebirr.crypto.ts b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.crypto.ts new file mode 100644 index 000000000..12ea9624a --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.crypto.ts @@ -0,0 +1,98 @@ +import * as crypto from 'crypto'; + +const EXCLUDE_FIELDS = new Set([ + 'sign', + 'sign_type', + 'header', + 'refund_info', + 'openType', + 'raw_request', + 'biz_content', +]); + +const NONCE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + +export function buildCanonicalString(requestObject: Record): string { + const fieldMap: Record = {}; + + for (const key of Object.keys(requestObject)) { + if (EXCLUDE_FIELDS.has(key)) continue; + fieldMap[key] = requestObject[key]; + } + + const biz = requestObject['biz_content']; + if (biz && typeof biz === 'object') { + for (const key of Object.keys(biz as Record)) { + if (EXCLUDE_FIELDS.has(key)) continue; + fieldMap[key] = (biz as Record)[key]; + } + } + + return Object.keys(fieldMap) + .sort() + .map((k) => `${k}=${fieldMap[k]}`) + .join('&'); +} + +export function signRequestObject( + requestObject: Record, + privateKey: string, +): string { + return signString(buildCanonicalString(requestObject), privateKey); +} + +export function verifyRequestObject( + requestObject: Record, + publicKey: string, +): boolean { + const signature = requestObject['sign']; + if (typeof signature !== 'string' || signature.length === 0) return false; + return verifySignature(buildCanonicalString(requestObject), signature, publicKey); +} + +export function signString(text: string, privateKey: string): string { + const signature = crypto.sign('sha256', Buffer.from(text), { + key: privateKey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST, + }); + return signature.toString('base64'); +} + +export function verifySignature( + text: string, + signatureBase64: string, + publicKey: string, +): boolean { + try { + return crypto.verify( + 'sha256', + Buffer.from(text), + { + key: publicKey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST, + }, + Buffer.from(signatureBase64, 'base64'), + ); + } catch { + return false; + } +} + +export function createTimestamp(): string { + return Math.round(Date.now() / 1000).toString(); +} + +export function createNonceStr(length = 32): string { + const bytes = crypto.randomBytes(length); + let out = ''; + for (let i = 0; i < length; i++) { + out += NONCE_CHARS[bytes[i] % NONCE_CHARS.length]; + } + return out; +} + +export function createMerchantOrderId(): string { + return `${Date.now()}${crypto.randomBytes(4).toString('hex')}`; +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts new file mode 100644 index 000000000..21980a175 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts @@ -0,0 +1,291 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { AxiosError, AxiosRequestConfig } from 'axios'; +import { firstValueFrom } from 'rxjs'; +import * as https from 'node:https'; +import { + PaymentProvider, + ProviderInitiationInput, + ProviderInitiationResult, + ProviderStatus, +} from '../payments.types'; +import { + createNonceStr, + createTimestamp, + signRequestObject, + verifyRequestObject, +} from './telebirr.crypto'; +import { + CreateOrderRequest, + CreateOrderResponse, + FabricTokenResponse, + QueryOrderResponse, +} from './telebirr.types'; + +const TELEBIRR_HTTP_TIMEOUT_MS = 10_000; + +@Injectable() +export class TelebirrProvider implements PaymentProvider { + readonly method = PaymentMethodType.TELEBIRR; + private readonly logger = new Logger(TelebirrProvider.name); + private readonly httpsAgent: https.Agent; + + constructor( + private readonly config: ConfigService, + private readonly http: HttpService, + ) { + const insecure = this.config.get('telebirr.insecureTls'); + if (insecure) { + this.logger.warn('TELEBIRR_INSECURE_TLS=true โ€” TLS verification disabled for Telebirr calls. DEV ONLY.'); + } + this.httpsAgent = new https.Agent({ + rejectUnauthorized: !insecure, + secureProtocol: 'TLSv1_2_method', + }); + } + + async initiate(input: ProviderInitiationInput): Promise { + const fabricToken = await this.applyFabricToken(); + const requestBody = this.buildCreateOrderRequest(input); + const response = await this.requestCreateOrder(fabricToken, requestBody); + + const prepayId = response.biz_content?.prepay_id; + if (!prepayId) { + throw new Error( + `Telebirr createOrder returned no prepay_id: ${JSON.stringify(response)}`, + ); + } + + const checkoutUrl = this.buildCheckoutUrl(prepayId); + const expiresAt = this.computeExpiresAt(requestBody.biz_content.timeout_express); + + return { + providerOrderId: prepayId, + clientAction: { type: 'REDIRECT', url: checkoutUrl }, + expiresAt, + rawInitiation: { + request: this.sanitize(requestBody), + response, + }, + }; + } + + async queryStatus(merchantOrderId: string): Promise { + const fabricToken = await this.applyFabricToken(); + const requestBody = this.buildQueryOrderRequest(merchantOrderId); + const response = await this.postJson( + `${this.baseUrl}/payment/v1/merchant/queryOrder`, + requestBody, + { + 'Content-Type': 'application/json', + 'X-APP-Key': this.fabricAppId, + Authorization: fabricToken, + }, + ); + + const tradeStatus = response.biz_content?.trade_status; + const providerTxnId = + response.biz_content?.trans_id ?? response.biz_content?.payment_order_id; + const mapped = this.mapTradeStatus(tradeStatus); + + return { + status: mapped, + providerTxnId, + failureCode: + mapped === PaymentIntentStatus.FAILED && tradeStatus ? tradeStatus : undefined, + rawResponse: response as Record, + }; + } + + mapTradeStatus(tradeStatus: string | undefined): PaymentIntentStatus { + switch (tradeStatus) { + case 'PAY_SUCCESS': + return PaymentIntentStatus.SUCCEEDED; + case 'PAY_FAILED': + case 'ORDER_CLOSED': + return PaymentIntentStatus.FAILED; + case 'WAIT_PAY': + return PaymentIntentStatus.REQUIRES_ACTION; + case 'PAYING': + return PaymentIntentStatus.PROCESSING; + default: + return PaymentIntentStatus.PROCESSING; + } + } + + mapWebhookTradeStatus(tradeStatus: string | undefined): PaymentIntentStatus { + switch (tradeStatus) { + case 'Completed': + return PaymentIntentStatus.SUCCEEDED; + case 'Failure': + case 'Expired': + return PaymentIntentStatus.FAILED; + case 'Paying': + case 'Pending': + return PaymentIntentStatus.PROCESSING; + default: + return PaymentIntentStatus.PROCESSING; + } + } + + verifyWebhookSignature(payload: Record): boolean { + if (!this.publicKey) { + this.logger.error('TELEBIRR_PUBLIC_KEY not configured; rejecting all webhooks'); + return false; + } + return verifyRequestObject(payload, this.publicKey); + } + + private async applyFabricToken(): Promise { + const response = await this.postJson( + `${this.baseUrl}/payment/v1/token`, + { appSecret: this.appSecret }, + { + 'Content-Type': 'application/json', + 'X-APP-Key': this.fabricAppId, + }, + ); + if (!response?.token) { + throw new Error(`Telebirr token request failed: ${JSON.stringify(response)}`); + } + return response.token; + } + + private async requestCreateOrder( + fabricToken: string, + body: CreateOrderRequest, + ): Promise { + return this.postJson( + `${this.baseUrl}/payment/v1/inapp/createOrder`, + body, + { + 'Content-Type': 'application/json', + 'X-APP-Key': this.fabricAppId, + Authorization: fabricToken, + }, + ); + } + + private buildCreateOrderRequest(input: ProviderInitiationInput): CreateOrderRequest { + const totalAmount = String(input.amountMinor / 100); + const req = { + timestamp: createTimestamp(), + nonce_str: createNonceStr(), + method: 'payment.preorder' as const, + version: '1.0' as const, + biz_content: { + notify_url: this.notifyUrl, + appid: this.merchantAppId, + merch_code: this.merchantCode, + merch_order_id: input.merchantOrderId, + trade_type: 'Checkout' as const, + title: `EDR Booking ${input.bookingRef}`, + total_amount: totalAmount, + trans_currency: input.currency, + timeout_express: this.timeoutExpress, + }, + }; + const sign = signRequestObject(req as unknown as Record, this.privateKey); + return { ...req, sign, sign_type: 'SHA256WithRSA' }; + } + + private buildQueryOrderRequest(merchantOrderId: string): Record { + const req = { + timestamp: createTimestamp(), + nonce_str: createNonceStr(), + method: 'payment.queryorder', + version: '1.0', + biz_content: { + appid: this.merchantAppId, + merch_code: this.merchantCode, + merch_order_id: merchantOrderId, + }, + }; + const sign = signRequestObject(req as Record, this.privateKey); + return { ...req, sign, sign_type: 'SHA256WithRSA' }; + } + + private buildCheckoutUrl(prepayId: string): string { + const map: Record = { + appid: this.merchantAppId, + merch_code: this.merchantCode, + nonce_str: createNonceStr(), + prepay_id: prepayId, + timestamp: createTimestamp(), + }; + const sign = signRequestObject(map, this.privateKey); + const rawRequest = [ + `appid=${map.appid}`, + `merch_code=${map.merch_code}`, + `nonce_str=${map.nonce_str}`, + `prepay_id=${map.prepay_id}`, + `timestamp=${map.timestamp}`, + 'sign_type=SHA256WithRSA', + `sign=${sign}`, + 'version=1.0', + 'trade_type=Checkout', + ].join('&'); + return `${this.webBaseUrl}${rawRequest}`; + } + + private computeExpiresAt(timeoutExpress: string): Date { + const match = /^(\d+)([smhd])$/.exec(timeoutExpress); + const minutes = match ? this.toMinutes(parseInt(match[1], 10), match[2]) : 15; + return new Date(Date.now() + minutes * 60_000); + } + + private toMinutes(n: number, unit: string): number { + switch (unit) { + case 's': return Math.max(1, Math.round(n / 60)); + case 'm': return n; + case 'h': return n * 60; + case 'd': return n * 60 * 24; + default: return 15; + } + } + + private async postJson( + url: string, + body: unknown, + headers: Record, + ): Promise { + const config: AxiosRequestConfig = { + headers, + timeout: TELEBIRR_HTTP_TIMEOUT_MS, + httpsAgent: this.httpsAgent, + }; + const started = Date.now(); + try { + const res = await firstValueFrom(this.http.post(url, body, config)); + this.logger.debug(`Telebirr POST ${url} status=${res.status} latency=${Date.now() - started}ms`); + return res.data; + } catch (err) { + if (err instanceof AxiosError) { + this.logger.error( + `Telebirr POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)} code=${err.code} message=${err.message}`, + ); + } else { + this.logger.error(`Telebirr POST ${url} threw: ${err instanceof Error ? err.message : err}`); + } + throw err; + } + } + + private sanitize(body: CreateOrderRequest): Record { + const { sign: _sign, ...rest } = body; + return rest; + } + + private get baseUrl(): string { return this.config.get('telebirr.baseUrl') ?? ''; } + private get webBaseUrl(): string { return this.config.get('telebirr.webBaseUrl') ?? ''; } + private get fabricAppId(): string { return this.config.get('telebirr.fabricAppId') ?? ''; } + private get appSecret(): string { return this.config.get('telebirr.appSecret') ?? ''; } + private get merchantAppId(): string { return this.config.get('telebirr.merchantAppId') ?? ''; } + private get merchantCode(): string { return this.config.get('telebirr.merchantCode') ?? ''; } + private get notifyUrl(): string { return this.config.get('telebirr.notifyUrl') ?? ''; } + private get timeoutExpress(): string { return this.config.get('telebirr.timeoutExpress') ?? '15m'; } + private get privateKey(): string { return this.config.get('telebirr.privateKey') ?? ''; } + private get publicKey(): string { return this.config.get('telebirr.publicKey') ?? ''; } +} diff --git a/apps/edr-passenger-api/src/modules/payments/providers/telebirr.types.ts b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.types.ts new file mode 100644 index 000000000..b540c1551 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.types.ts @@ -0,0 +1,69 @@ +export interface FabricTokenResponse { + token: string; + expires_in?: number | string; +} + +export interface CreateOrderBizContent { + notify_url: string; + appid: string; + merch_code: string; + merch_order_id: string; + trade_type: 'Checkout' | 'InApp' | 'MiniApp'; + title: string; + total_amount: string; + trans_currency: string; + timeout_express: string; +} + +export interface CreateOrderRequest { + timestamp: string; + nonce_str: string; + method: 'payment.preorder'; + version: '1.0'; + biz_content: CreateOrderBizContent; + sign: string; + sign_type: 'SHA256WithRSA'; +} + +export interface CreateOrderResponse { + code?: string; + msg?: string; + biz_content?: { + prepay_id?: string; + receiveCode?: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +export type TelebirrTradeStatus = + | 'PAY_SUCCESS' + | 'PAY_FAILED' + | 'WAIT_PAY' + | 'ORDER_CLOSED' + | 'PAYING' + | 'ACCEPTED' + | 'REFUNDING' + | 'REFUND_SUCCESS' + | 'REFUND_FAILED'; + +export interface QueryOrderResponse { + result?: 'SUCCESS' | 'FAIL'; + code?: string; + msg?: string; + nonce_str?: string; + sign?: string; + sign_type?: string; + biz_content?: { + merch_order_id?: string; + order_status?: string; + trade_status?: TelebirrTradeStatus | string; + payment_order_id?: string; + trans_id?: string; + trans_time?: string; + trans_currency?: string; + total_amount?: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} diff --git a/apps/edr-passenger-api/src/modules/payments/webhooks/card-webhook.service.ts b/apps/edr-passenger-api/src/modules/payments/webhooks/card-webhook.service.ts new file mode 100644 index 000000000..5612ac77d --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/webhooks/card-webhook.service.ts @@ -0,0 +1,147 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { PrismaService } from '../../../common/prisma.service'; +import { PaymentsService } from '../payments.service'; +import { CardProvider } from '../providers/card.provider'; + +export interface CardWebhookPayload { + id: string; + type: string; + data: { + object: { + id: string; + status: string; + amount: number; + currency: string; + metadata: { + merchantOrderId: string; + bookingRef: string; + }; + transaction_id?: string; + paid_at?: number; + failure_code?: string; + failure_message?: string; + }; + }; + created: number; +} + +@Injectable() +export class CardWebhookService { + private readonly logger = new Logger(CardWebhookService.name); + + constructor( + private readonly prisma: PrismaService, + private readonly provider: CardProvider, + private readonly payments: PaymentsService, + ) {} + + async handle(payload: CardWebhookPayload, signature: string): Promise { + const merchantOrderId = payload.data.object.metadata.merchantOrderId; + const externalEventId = `${payload.id}_${payload.type}`; + const signatureValid = this.provider.verifyWebhookSignature( + payload as unknown as Record, + signature, + ); + + const eventRow = await this.persistEvent({ + externalEventId, + merchantOrderId, + providerTxnId: payload.data.object.transaction_id, + signatureValid, + status: payload.data.object.status, + payload, + }); + + if (!eventRow) { + this.logger.log(`Card webhook duplicate: ${externalEventId} โ€” short-circuit OK`); + return; + } + + if (!signatureValid) { + this.logger.warn(`Card webhook signature invalid for merchantOrderId=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'signature-invalid'); + return; + } + + const intent = await this.prisma.paymentIntent.findUnique({ + where: { merchantOrderId }, + }); + if (!intent) { + this.logger.warn(`Card webhook: no PaymentIntent for merchantOrderId=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'intent-not-found'); + return; + } + + const mapped = this.provider.mapWebhookStatus(payload.data.object.status); + + try { + if (mapped === PaymentIntentStatus.SUCCEEDED) { + await this.payments.finalizePaymentSuccess({ + intentId: intent.id, + providerTxnId: payload.data.object.transaction_id, + paidAt: payload.data.object.paid_at ? new Date(payload.data.object.paid_at * 1000) : undefined, + }); + } else if (mapped === PaymentIntentStatus.FAILED) { + await this.payments.markPaymentFailed({ + intentId: intent.id, + failureCode: payload.data.object.failure_code, + failureMessage: payload.data.object.failure_message, + }); + } else { + await this.prisma.paymentIntent.update({ + where: { id: intent.id }, + data: { + status: mapped, + providerTxnId: payload.data.object.transaction_id ?? undefined, + }, + }); + } + await this.markProcessed(eventRow.id); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`Card webhook processing failed for ${merchantOrderId}: ${message}`); + await this.markProcessed(eventRow.id, `processing-error: ${message}`); + throw err; + } + } + + private async persistEvent(input: { + externalEventId: string; + merchantOrderId: string; + providerTxnId?: string; + signatureValid: boolean; + status: string; + payload: CardWebhookPayload; + }): Promise<{ id: string } | null> { + try { + return await this.prisma.paymentWebhookEvent.create({ + data: { + provider: PaymentMethodType.CARD, + externalEventId: input.externalEventId, + merchantOrderId: input.merchantOrderId, + providerTxnId: input.providerTxnId, + signatureValid: input.signatureValid, + status: input.status, + payload: input.payload as unknown as Prisma.InputJsonValue, + }, + select: { id: true }, + }); + } catch (err) { + if ( + err instanceof Prisma.PrismaClientKnownRequestError && + err.code === 'P2002' + ) { + return null; + } + throw err; + } + } + + private async markProcessed(eventId: string, processingError?: string): Promise { + await this.prisma.paymentWebhookEvent.update({ + where: { id: eventId }, + data: { processedAt: new Date(), processingError }, + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/webhooks/cbe-birr-webhook.service.ts b/apps/edr-passenger-api/src/modules/payments/webhooks/cbe-birr-webhook.service.ts new file mode 100644 index 000000000..2c34e85f6 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/webhooks/cbe-birr-webhook.service.ts @@ -0,0 +1,133 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { PrismaService } from '../../../common/prisma.service'; +import { PaymentsService } from '../payments.service'; +import { CbeBirrProvider } from '../providers/cbe-birr.provider'; + +export interface CbeBirrWebhookPayload { + merchantId: string; + merchantOrderId: string; + orderId: string; + status: string; + transactionId?: string; + amount?: string; + currency?: string; + paidAt?: string; + signature: string; + [key: string]: unknown; +} + +@Injectable() +export class CbeBirrWebhookService { + private readonly logger = new Logger(CbeBirrWebhookService.name); + + constructor( + private readonly prisma: PrismaService, + private readonly provider: CbeBirrProvider, + private readonly payments: PaymentsService, + ) {} + + async handle(payload: CbeBirrWebhookPayload): Promise { + const merchantOrderId = payload.merchantOrderId; + const externalEventId = `${payload.orderId}_${payload.status}`; + const signatureValid = this.provider.verifyWebhookSignature( + payload as unknown as Record, + ); + + const eventRow = await this.persistEvent({ + externalEventId, + merchantOrderId, + providerTxnId: payload.transactionId ?? payload.orderId, + signatureValid, + status: payload.status, + payload, + }); + + if (!eventRow) { + this.logger.log(`CBE Birr webhook duplicate: ${externalEventId} โ€” short-circuit OK`); + return; + } + + if (!signatureValid) { + this.logger.warn(`CBE Birr webhook signature invalid for merchantOrderId=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'signature-invalid'); + return; + } + + const intent = await this.prisma.paymentIntent.findUnique({ + where: { merchantOrderId }, + }); + if (!intent) { + this.logger.warn(`CBE Birr webhook: no PaymentIntent for merchantOrderId=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'intent-not-found'); + return; + } + + const mapped = this.provider.mapWebhookStatus(payload.status); + + try { + if (mapped === PaymentIntentStatus.SUCCEEDED) { + await this.payments.finalizePaymentSuccess({ + intentId: intent.id, + providerTxnId: payload.transactionId ?? payload.orderId, + paidAt: payload.paidAt ? new Date(payload.paidAt) : undefined, + }); + } else if (mapped === PaymentIntentStatus.FAILED) { + await this.payments.markPaymentFailed({ + intentId: intent.id, + failureCode: payload.status, + }); + } else { + await this.prisma.paymentIntent.update({ + where: { id: intent.id }, + data: { status: mapped, providerTxnId: payload.transactionId ?? undefined }, + }); + } + await this.markProcessed(eventRow.id); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`CBE Birr webhook processing failed for ${merchantOrderId}: ${message}`); + await this.markProcessed(eventRow.id, `processing-error: ${message}`); + throw err; + } + } + + private async persistEvent(input: { + externalEventId: string; + merchantOrderId: string; + providerTxnId?: string; + signatureValid: boolean; + status: string; + payload: CbeBirrWebhookPayload; + }): Promise<{ id: string } | null> { + try { + return await this.prisma.paymentWebhookEvent.create({ + data: { + provider: PaymentMethodType.CBE_BIRR, + externalEventId: input.externalEventId, + merchantOrderId: input.merchantOrderId, + providerTxnId: input.providerTxnId, + signatureValid: input.signatureValid, + status: input.status, + payload: input.payload as unknown as Prisma.InputJsonValue, + }, + select: { id: true }, + }); + } catch (err) { + if ( + err instanceof Prisma.PrismaClientKnownRequestError && + err.code === 'P2002' + ) { + return null; + } + throw err; + } + } + + private async markProcessed(eventId: string, processingError?: string): Promise { + await this.prisma.paymentWebhookEvent.update({ + where: { id: eventId }, + data: { processedAt: new Date(), processingError }, + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/webhooks/ebirr-webhook.service.ts b/apps/edr-passenger-api/src/modules/payments/webhooks/ebirr-webhook.service.ts new file mode 100644 index 000000000..700078b79 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/webhooks/ebirr-webhook.service.ts @@ -0,0 +1,133 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { PrismaService } from '../../../common/prisma.service'; +import { PaymentsService } from '../payments.service'; +import { EBirrProvider } from '../providers/ebirr.provider'; + +export interface EBirrWebhookPayload { + merchantCode: string; + orderNo: string; + tradeStatus: string; + tradeNo?: string; + totalAmount?: number; + currency?: string; + payTime?: number; + timestamp: number; + sign: string; + [key: string]: unknown; +} + +@Injectable() +export class EBirrWebhookService { + private readonly logger = new Logger(EBirrWebhookService.name); + + constructor( + private readonly prisma: PrismaService, + private readonly provider: EBirrProvider, + private readonly payments: PaymentsService, + ) {} + + async handle(payload: EBirrWebhookPayload): Promise { + const merchantOrderId = payload.orderNo; + const externalEventId = `${payload.orderNo}_${payload.tradeStatus}_${payload.timestamp}`; + const signatureValid = this.provider.verifyWebhookSignature( + payload as unknown as Record, + ); + + const eventRow = await this.persistEvent({ + externalEventId, + merchantOrderId, + providerTxnId: payload.tradeNo, + signatureValid, + status: payload.tradeStatus, + payload, + }); + + if (!eventRow) { + this.logger.log(`eBirr webhook duplicate: ${externalEventId} โ€” short-circuit OK`); + return; + } + + if (!signatureValid) { + this.logger.warn(`eBirr webhook signature invalid for orderNo=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'signature-invalid'); + return; + } + + const intent = await this.prisma.paymentIntent.findUnique({ + where: { merchantOrderId }, + }); + if (!intent) { + this.logger.warn(`eBirr webhook: no PaymentIntent for orderNo=${merchantOrderId}`); + await this.markProcessed(eventRow.id, 'intent-not-found'); + return; + } + + const mapped = this.provider.mapWebhookStatus(payload.tradeStatus); + + try { + if (mapped === PaymentIntentStatus.SUCCEEDED) { + await this.payments.finalizePaymentSuccess({ + intentId: intent.id, + providerTxnId: payload.tradeNo, + paidAt: payload.payTime ? new Date(payload.payTime) : undefined, + }); + } else if (mapped === PaymentIntentStatus.FAILED) { + await this.payments.markPaymentFailed({ + intentId: intent.id, + failureCode: payload.tradeStatus, + }); + } else { + await this.prisma.paymentIntent.update({ + where: { id: intent.id }, + data: { status: mapped, providerTxnId: payload.tradeNo ?? undefined }, + }); + } + await this.markProcessed(eventRow.id); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`eBirr webhook processing failed for ${merchantOrderId}: ${message}`); + await this.markProcessed(eventRow.id, `processing-error: ${message}`); + throw err; + } + } + + private async persistEvent(input: { + externalEventId: string; + merchantOrderId: string; + providerTxnId?: string; + signatureValid: boolean; + status: string; + payload: EBirrWebhookPayload; + }): Promise<{ id: string } | null> { + try { + return await this.prisma.paymentWebhookEvent.create({ + data: { + provider: PaymentMethodType.EBIRR, + externalEventId: input.externalEventId, + merchantOrderId: input.merchantOrderId, + providerTxnId: input.providerTxnId, + signatureValid: input.signatureValid, + status: input.status, + payload: input.payload as unknown as Prisma.InputJsonValue, + }, + select: { id: true }, + }); + } catch (err) { + if ( + err instanceof Prisma.PrismaClientKnownRequestError && + err.code === 'P2002' + ) { + return null; + } + throw err; + } + } + + private async markProcessed(eventId: string, processingError?: string): Promise { + await this.prisma.paymentWebhookEvent.update({ + where: { id: eventId }, + data: { processedAt: new Date(), processingError }, + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/webhooks/telebirr-webhook.service.ts b/apps/edr-passenger-api/src/modules/payments/webhooks/telebirr-webhook.service.ts new file mode 100644 index 000000000..bb9b2127e --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/webhooks/telebirr-webhook.service.ts @@ -0,0 +1,153 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; +import { PrismaService } from '../../../common/prisma.service'; +import { PaymentsService } from '../payments.service'; +import { TelebirrProvider } from '../providers/telebirr.provider'; + +export interface TelebirrWebhookPayload { + merch_order_id: string; + payment_order_id: string; + trade_status: string; + trans_id?: string; + total_amount?: string; + trans_currency?: string; + notify_time?: string; + trans_end_time?: string; + sign: string; + sign_type?: string; + [key: string]: unknown; +} + +@Injectable() +export class TelebirrWebhookService { + private readonly logger = new Logger(TelebirrWebhookService.name); + + constructor( + private readonly prisma: PrismaService, + private readonly provider: TelebirrProvider, + private readonly payments: PaymentsService, + ) {} + + async handle(payload: TelebirrWebhookPayload): Promise { + const merchantOrderId = payload.merch_order_id; + const externalEventId = this.buildExternalEventId(payload); + const signatureValid = this.provider.verifyWebhookSignature( + payload as unknown as Record, + ); + + const eventRow = await this.persistEvent({ + externalEventId, + merchantOrderId, + providerTxnId: payload.trans_id ?? payload.payment_order_id, + signatureValid, + status: payload.trade_status, + payload, + }); + + if (!eventRow) { + this.logger.log( + `Telebirr webhook duplicate: ${externalEventId} โ€” short-circuit OK`, + ); + return; + } + + if (!signatureValid) { + this.logger.warn( + `Telebirr webhook signature invalid for merch_order_id=${merchantOrderId}`, + ); + await this.markProcessed(eventRow.id, 'signature-invalid'); + return; + } + + const intent = await this.prisma.paymentIntent.findUnique({ + where: { merchantOrderId }, + }); + if (!intent) { + this.logger.warn( + `Telebirr webhook: no PaymentIntent for merch_order_id=${merchantOrderId}`, + ); + await this.markProcessed(eventRow.id, 'intent-not-found'); + return; + } + + const mapped = this.provider.mapWebhookTradeStatus(payload.trade_status); + + try { + if (mapped === PaymentIntentStatus.SUCCEEDED) { + await this.payments.finalizePaymentSuccess({ + intentId: intent.id, + providerTxnId: payload.trans_id ?? payload.payment_order_id, + paidAt: this.parseEpochSeconds(payload.trans_end_time), + }); + } else if (mapped === PaymentIntentStatus.FAILED) { + await this.payments.markPaymentFailed({ + intentId: intent.id, + failureCode: payload.trade_status, + }); + } else { + await this.prisma.paymentIntent.update({ + where: { id: intent.id }, + data: { status: mapped, providerTxnId: payload.trans_id ?? undefined }, + }); + } + await this.markProcessed(eventRow.id); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error( + `Telebirr webhook processing failed for ${merchantOrderId}: ${message}`, + ); + await this.markProcessed(eventRow.id, `processing-error: ${message}`); + throw err; + } + } + + private buildExternalEventId(payload: TelebirrWebhookPayload): string { + return `${payload.payment_order_id}_${payload.trade_status}`; + } + + private async persistEvent(input: { + externalEventId: string; + merchantOrderId: string; + providerTxnId?: string; + signatureValid: boolean; + status: string; + payload: TelebirrWebhookPayload; + }): Promise<{ id: string } | null> { + try { + return await this.prisma.paymentWebhookEvent.create({ + data: { + provider: PaymentMethodType.TELEBIRR, + externalEventId: input.externalEventId, + merchantOrderId: input.merchantOrderId, + providerTxnId: input.providerTxnId, + signatureValid: input.signatureValid, + status: input.status, + payload: input.payload as unknown as Prisma.InputJsonValue, + }, + select: { id: true }, + }); + } catch (err) { + if ( + err instanceof Prisma.PrismaClientKnownRequestError && + err.code === 'P2002' + ) { + return null; + } + throw err; + } + } + + private async markProcessed(eventId: string, processingError?: string): Promise { + await this.prisma.paymentWebhookEvent.update({ + where: { id: eventId }, + data: { processedAt: new Date(), processingError }, + }); + } + + private parseEpochSeconds(raw: string | undefined): Date | undefined { + if (!raw) return undefined; + const n = parseInt(raw, 10); + if (Number.isNaN(n)) return undefined; + return new Date(n * 1000); + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/webhooks/webhooks.controller.ts b/apps/edr-passenger-api/src/modules/payments/webhooks/webhooks.controller.ts new file mode 100644 index 000000000..4edbe58a8 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/webhooks/webhooks.controller.ts @@ -0,0 +1,86 @@ +import { Body, Controller, Headers, HttpCode, HttpStatus, Logger, Post } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + TelebirrWebhookPayload, + TelebirrWebhookService, +} from './telebirr-webhook.service'; +import { + CbeBirrWebhookPayload, + CbeBirrWebhookService, +} from './cbe-birr-webhook.service'; +import { + EBirrWebhookPayload, + EBirrWebhookService, +} from './ebirr-webhook.service'; +import { + CardWebhookPayload, + CardWebhookService, +} from './card-webhook.service'; + +@ApiTags('Payment Webhooks') +@Controller('payments/webhooks') +export class WebhooksController { + private readonly logger = new Logger(WebhooksController.name); + + constructor( + private readonly telebirr: TelebirrWebhookService, + private readonly cbeBirr: CbeBirrWebhookService, + private readonly eBirr: EBirrWebhookService, + private readonly card: CardWebhookService, + ) {} + + @Post('telebirr') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Telebirr payment notification callback' }) + async receiveTelebirr(@Body() payload: TelebirrWebhookPayload) { + try { + await this.telebirr.handle(payload); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`Telebirr webhook handler threw: ${message}`); + } + return { code: '0', message: 'OK' }; + } + + @Post('cbe-birr') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'CBE Birr payment notification callback' }) + async receiveCbeBirr(@Body() payload: CbeBirrWebhookPayload) { + try { + await this.cbeBirr.handle(payload); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`CBE Birr webhook handler threw: ${message}`); + } + return { success: true }; + } + + @Post('ebirr') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'eBirr payment notification callback' }) + async receiveEBirr(@Body() payload: EBirrWebhookPayload) { + try { + await this.eBirr.handle(payload); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`eBirr webhook handler threw: ${message}`); + } + return { code: '0000', message: 'success' }; + } + + @Post('card') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Card payment notification callback' }) + async receiveCard( + @Body() payload: CardWebhookPayload, + @Headers('stripe-signature') signature: string, + ) { + try { + await this.card.handle(payload, signature); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error(`Card webhook handler threw: ${message}`); + } + return { received: true }; + } +} diff --git a/apps/edr-passenger-api/src/modules/reports/reports.controller.ts b/apps/edr-passenger-api/src/modules/reports/reports.controller.ts new file mode 100644 index 000000000..772a1428c --- /dev/null +++ b/apps/edr-passenger-api/src/modules/reports/reports.controller.ts @@ -0,0 +1,35 @@ +import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ReportsService } from './reports.service'; +import { GenerateReportDto } from './reports.dto'; +import { IamGuard, IamRoles } from '../../common/iam-adapter'; +import { UserRole } from '@prisma/client'; + +@ApiTags('Reports') +@Controller('reports') +@UseGuards(IamGuard) +@ApiBearerAuth('IAM-auth') +export class ReportsController { + constructor(private service: ReportsService) {} + + @Post('generate') + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'Generate operational report' }) + generateReport(@Body() dto: GenerateReportDto) { + return this.service.generateReport(dto); + } + + @Get(':reportId') + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'Get report by ID' }) + getReport(@Param('reportId') reportId: string) { + return this.service.getReport(reportId); + } + + @Get() + @IamRoles('ADMIN', 'SUPERVISOR') + @ApiOperation({ summary: 'List reports' }) + listReports(@Query('type') type?: string) { + return this.service.listReports(type); + } +} diff --git a/apps/edr-passenger-api/src/modules/reports/reports.dto.ts b/apps/edr-passenger-api/src/modules/reports/reports.dto.ts new file mode 100644 index 000000000..5fdb856ec --- /dev/null +++ b/apps/edr-passenger-api/src/modules/reports/reports.dto.ts @@ -0,0 +1,29 @@ +import { IsString, IsDateString, IsOptional, IsEnum } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export enum ReportType { + REVENUE = 'REVENUE', + OCCUPANCY = 'OCCUPANCY', + AGENT_SALES = 'AGENT_SALES', + CANCELLATIONS = 'CANCELLATIONS', + PAYMENT_METHODS = 'PAYMENT_METHODS' +} + +export enum ExportFormat { + JSON = 'JSON', + CSV = 'CSV', + PDF = 'PDF' +} + +export class GenerateReportDto { + @ApiProperty({ enum: ReportType }) @IsEnum(ReportType) reportType: ReportType; + @ApiProperty({ example: '2026-01-01' }) @IsDateString() dateFrom: string; + @ApiProperty({ example: '2026-01-31' }) @IsDateString() dateTo: string; + @ApiPropertyOptional() @IsOptional() @IsString() routeId?: string; + @ApiPropertyOptional() @IsOptional() @IsString() agentId?: string; +} + +export class ExportReportDto { + @ApiProperty() @IsString() reportId: string; + @ApiProperty({ enum: ExportFormat }) @IsEnum(ExportFormat) format: ExportFormat; +} diff --git a/apps/edr-passenger-api/src/modules/reports/reports.module.ts b/apps/edr-passenger-api/src/modules/reports/reports.module.ts new file mode 100644 index 000000000..801120cbf --- /dev/null +++ b/apps/edr-passenger-api/src/modules/reports/reports.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { ReportsController } from './reports.controller'; +import { ReportsService } from './reports.service'; + +@Module({ + imports: [HttpModule], + controllers: [ReportsController], + providers: [ReportsService], + exports: [ReportsService] +}) +export class ReportsModule {} diff --git a/apps/edr-passenger-api/src/modules/reports/reports.service.ts b/apps/edr-passenger-api/src/modules/reports/reports.service.ts new file mode 100644 index 000000000..29bc82a55 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/reports/reports.service.ts @@ -0,0 +1,171 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { GenerateReportDto, ReportType } from './reports.dto'; + +@Injectable() +export class ReportsService { + constructor(private prisma: PrismaService) {} + + async generateReport(dto: GenerateReportDto) { + const dateFrom = new Date(dto.dateFrom); + const dateTo = new Date(dto.dateTo); + + let data: any; + switch (dto.reportType) { + case ReportType.REVENUE: + data = await this.generateRevenueReport(dateFrom, dateTo); + break; + case ReportType.OCCUPANCY: + data = await this.generateOccupancyReport(dateFrom, dateTo); + break; + case ReportType.AGENT_SALES: + data = await this.generateAgentSalesReport(dateFrom, dateTo, dto.agentId); + break; + case ReportType.CANCELLATIONS: + data = await this.generateCancellationsReport(dateFrom, dateTo); + break; + case ReportType.PAYMENT_METHODS: + data = await this.generatePaymentMethodsReport(dateFrom, dateTo); + break; + default: + data = {}; + } + + const report = await this.prisma.operationalReport.create({ + data: { + reportType: dto.reportType, + dateFrom, + dateTo, + data + } + }); + + return { reportId: report.id, reportType: dto.reportType, data }; + } + + private async generateRevenueReport(dateFrom: Date, dateTo: Date) { + const bookings = await this.prisma.booking.findMany({ + where: { + createdAt: { gte: dateFrom, lte: dateTo }, + status: { in: ['CONFIRMED', 'COMPLETED'] } + }, + include: { paymentIntent: true } + }); + + const totalRevenue = bookings.reduce((sum, b) => sum + b.totalMinor, 0); + const byPaymentMethod = bookings.reduce((acc, b) => { + const method = b.paymentIntent?.method ?? 'UNKNOWN'; + acc[method] = (acc[method] || 0) + b.totalMinor; + return acc; + }, {} as Record); + + return { + totalBookings: bookings.length, + totalRevenueMinor: totalRevenue, + totalRevenue: totalRevenue / 100, + currency: 'ETB', + byPaymentMethod + }; + } + + private async generateOccupancyReport(dateFrom: Date, dateTo: Date) { + const schedules = await this.prisma.trainSchedule.findMany({ + where: { departureAt: { gte: dateFrom, lte: dateTo } }, + include: { + coachAssignments: { include: { coach: { include: { seats: true } } } }, + bookings: { where: { status: { in: ['CONFIRMED', 'COMPLETED'] } }, include: { seats: true } }, + }, + }); + + const tripData = schedules.map(schedule => { + const totalSeats = schedule.coachAssignments.reduce((sum, a) => sum + a.coach.seats.length, 0); + const bookedSeats = schedule.bookings.reduce((sum, b) => sum + b.seats.length, 0); + const occupancyRate = totalSeats > 0 ? (bookedSeats / totalSeats) * 100 : 0; + return { scheduleId: schedule.id, departureAt: schedule.departureAt, totalSeats, bookedSeats, occupancyRate: +occupancyRate.toFixed(2) }; + }); + + const avgOccupancy = tripData.length > 0 ? tripData.reduce((sum, t) => sum + t.occupancyRate, 0) / tripData.length : 0; + return { totalSchedules: schedules.length, averageOccupancyRate: +avgOccupancy.toFixed(2), schedules: tripData }; + } + + private async generateAgentSalesReport(dateFrom: Date, dateTo: Date, agentId?: string) { + const agentBookings = await this.prisma.agentBooking.findMany({ + where: { + createdAt: { gte: dateFrom, lte: dateTo }, + ...(agentId ? { agentId } : {}) + }, + include: { + agent: { include: { user: true } }, + booking: true + } + }); + + const byAgent = agentBookings.reduce((acc, ab) => { + const agentName = ab.agent.user.fullName; + if (!acc[agentName]) { + acc[agentName] = { bookings: 0, revenueMinor: 0, cashCollected: 0 }; + } + acc[agentName].bookings += 1; + acc[agentName].revenueMinor += ab.booking.totalMinor; + acc[agentName].cashCollected += ab.cashReceived ?? 0; + return acc; + }, {} as Record); + + return { + totalAgentBookings: agentBookings.length, + byAgent + }; + } + + private async generateCancellationsReport(dateFrom: Date, dateTo: Date) { + const cancellations = await this.prisma.bookingCancellation.findMany({ + where: { createdAt: { gte: dateFrom, lte: dateTo } }, + include: { booking: true } + }); + + const totalRefunded = cancellations.reduce((sum, c) => sum + c.refundAmount, 0); + + return { + totalCancellations: cancellations.length, + totalRefundedMinor: totalRefunded, + totalRefunded: totalRefunded / 100, + currency: 'ETB' + }; + } + + private async generatePaymentMethodsReport(dateFrom: Date, dateTo: Date) { + const payments = await this.prisma.paymentIntent.findMany({ + where: { + createdAt: { gte: dateFrom, lte: dateTo }, + status: 'SUCCEEDED' + } + }); + + const byMethod = payments.reduce((acc, p) => { + const method = p.method; + if (!acc[method]) { + acc[method] = { count: 0, totalMinor: 0 }; + } + acc[method].count += 1; + acc[method].totalMinor += p.amountMinor; + return acc; + }, {} as Record); + + return { + totalPayments: payments.length, + byMethod + }; + } + + async getReport(reportId: string) { + return this.prisma.operationalReport.findUnique({ where: { id: reportId } }); + } + + async listReports(reportType?: string) { + return this.prisma.operationalReport.findMany({ + where: reportType ? { reportType } : {}, + orderBy: { createdAt: 'desc' }, + take: 50 + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/schedules/routes.controller.ts b/apps/edr-passenger-api/src/modules/schedules/routes.controller.ts new file mode 100644 index 000000000..75224c3f9 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/schedules/routes.controller.ts @@ -0,0 +1,88 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, ParseIntPipe, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; +import { RoutesService } from './routes.service'; +import { CreateRouteDto, AddRouteStopDto, UpdateRouteDto } from './routes.dto'; +import { JwtGuard } from '../../common/jwt.guard'; + +@ApiTags('Routes') +@Controller('routes') +export class RoutesController { + constructor(private service: RoutesService) {} + + // โ”€โ”€ Routes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + @Post() + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ + summary: 'Create a reusable route with its ordered stops', + description: `Define the physical corridor once (e.g. ADDโ†’ADMโ†’AWSโ†’DDWโ†’AYSโ†’DJI). +Schedules reference this route via routeId and supply actual planned times per stop. +Route stops carry distanceKm for fare-by-distance calculations.`, + }) + @ApiResponse({ status: 201, description: 'Route created with stops' }) + @ApiResponse({ status: 409, description: 'Route code already exists or duplicate sequences' }) + @ApiResponse({ status: 400, description: 'Fewer than 2 stops or invalid station IDs' }) + createRoute(@Body() dto: CreateRouteDto) { return this.service.createRoute(dto); } + + @Get() + @ApiOperation({ summary: 'List all routes' }) + @ApiQuery({ name: 'activeOnly', required: false, type: Boolean, description: 'Filter to active routes only' }) + @ApiResponse({ status: 200, description: 'Array of routes with stop count' }) + listRoutes(@Query('activeOnly') activeOnly?: string) { + return this.service.listRoutes(activeOnly === 'true'); + } + + @Get(':id') + @ApiOperation({ summary: 'Get route with all stops and station details' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiResponse({ status: 200, description: 'Route with enriched stop list (station name, code, city)' }) + @ApiResponse({ status: 404, description: 'Route not found' }) + getRoute(@Param('id') id: string) { return this.service.getRoute(id); } + + @Patch(':id') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update route metadata (name, description, active flag, effectiveUntil)' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiResponse({ status: 200, description: 'Route updated' }) + @ApiResponse({ status: 404, description: 'Route not found' }) + updateRoute(@Param('id') id: string, @Body() dto: UpdateRouteDto) { return this.service.updateRoute(id, dto); } + + // โ”€โ”€ Route Stops โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + @Get(':id/stops') + @ApiOperation({ summary: 'List all stops for a route ordered by sequence' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiResponse({ status: 200, description: 'Ordered stop list with station details' }) + @ApiResponse({ status: 404, description: 'Route not found' }) + getStops(@Param('id') id: string) { return this.service.getStops(id); } + + @Post(':id/stops') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Add a stop to an existing route' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiResponse({ status: 201, description: 'Stop added' }) + @ApiResponse({ status: 409, description: 'Sequence already exists on this route' }) + @ApiResponse({ status: 404, description: 'Route or station not found' }) + addStop(@Param('id') id: string, @Body() dto: AddRouteStopDto) { return this.service.addStop(id, dto); } + + @Delete(':id/stops/:sequence') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Remove a stop from a route by sequence number' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiParam({ name: 'sequence', description: 'Stop sequence number to remove' }) + @ApiResponse({ status: 200, description: 'Stop removed' }) + @ApiResponse({ status: 400, description: 'Cannot remove โ€” route would have fewer than 2 stops' }) + @ApiResponse({ status: 404, description: 'Stop not found' }) + removeStop(@Param('id') id: string, @Param('sequence', ParseIntPipe) sequence: number) { + return this.service.removeStop(id, sequence); + } + + // โ”€โ”€ Schedules for a Route โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + @Get(':id/schedules') + @ApiOperation({ summary: 'List all train schedules that use this route' }) + @ApiParam({ name: 'id', description: 'Route UUID' }) + @ApiResponse({ status: 200, description: 'Schedules with train and terminal station details' }) + @ApiResponse({ status: 404, description: 'Route not found' }) + getSchedules(@Param('id') id: string) { return this.service.getSchedulesForRoute(id); } +} diff --git a/apps/edr-passenger-api/src/modules/schedules/routes.dto.ts b/apps/edr-passenger-api/src/modules/schedules/routes.dto.ts new file mode 100644 index 000000000..230dad3f8 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/schedules/routes.dto.ts @@ -0,0 +1,44 @@ +import { IsString, IsInt, IsOptional, IsArray, ValidateNested, IsBoolean, IsDateString, Min } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; + +export class RouteStopInputDto { + @ApiProperty({ example: 'station-uuid', description: 'Station UUID' }) @IsString() stationId: string; + @ApiProperty({ example: 1, description: 'Stop order (1 = origin, ascending)' }) @IsInt() @Min(1) sequence: number; + @ApiPropertyOptional({ example: 120, description: 'Distance in km from previous stop' }) @IsOptional() @IsInt() distanceKm?: number; +} + +export class CreateRouteDto { + @ApiProperty({ example: 'ADD-DJI', description: 'Unique route code' }) @IsString() code: string; + @ApiProperty({ example: 'Addis Ababa โ€“ Djibouti' }) @IsString() name: string; + @ApiPropertyOptional({ example: 'Main corridor via Dire Dawa' }) @IsOptional() @IsString() description?: string; + @ApiProperty({ example: '2026-01-01T00:00:00Z', description: 'Date from which this route is effective' }) @IsDateString() effectiveFrom: string; + @ApiPropertyOptional({ example: '2027-12-31T23:59:59Z' }) @IsOptional() @IsDateString() effectiveUntil?: string; + @ApiProperty({ + type: [RouteStopInputDto], + description: 'Ordered stops for this route. Sequence 1 = origin, last sequence = destination.', + example: [ + { stationId: 'uuid-ADD', sequence: 1 }, + { stationId: 'uuid-ADM', sequence: 2, distanceKm: 99 }, + { stationId: 'uuid-AWS', sequence: 3, distanceKm: 120 }, + { stationId: 'uuid-DDW', sequence: 4, distanceKm: 180 }, + { stationId: 'uuid-AYS', sequence: 5, distanceKm: 95 }, + { stationId: 'uuid-DJI', sequence: 6, distanceKm: 60 }, + ], + }) + @IsArray() @ValidateNested({ each: true }) @Type(() => RouteStopInputDto) + stops: RouteStopInputDto[]; +} + +export class AddRouteStopDto { + @ApiProperty({ example: 'station-uuid' }) @IsString() stationId: string; + @ApiProperty({ example: 3 }) @IsInt() @Min(1) sequence: number; + @ApiPropertyOptional({ example: 75 }) @IsOptional() @IsInt() distanceKm?: number; +} + +export class UpdateRouteDto { + @ApiPropertyOptional({ example: 'Addis Ababa โ€“ Djibouti Express' }) @IsOptional() @IsString() name?: string; + @ApiPropertyOptional() @IsOptional() @IsString() description?: string; + @ApiPropertyOptional({ example: true }) @IsOptional() @IsBoolean() active?: boolean; + @ApiPropertyOptional({ example: '2027-12-31T23:59:59Z' }) @IsOptional() @IsDateString() effectiveUntil?: string; +} diff --git a/apps/edr-passenger-api/src/modules/schedules/routes.service.ts b/apps/edr-passenger-api/src/modules/schedules/routes.service.ts new file mode 100644 index 000000000..38705f435 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/schedules/routes.service.ts @@ -0,0 +1,197 @@ +import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { CreateRouteDto, AddRouteStopDto, UpdateRouteDto } from './routes.dto'; + +@Injectable() +export class RoutesService { + constructor(private prisma: PrismaService) {} + + // โ”€โ”€ Route CRUD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + async createRoute(dto: CreateRouteDto) { + const existing = await this.prisma.route.findUnique({ where: { code: dto.code } }); + if (existing) throw new ConflictException(`Route code "${dto.code}" already exists`); + + if (dto.stops.length < 2) throw new BadRequestException('A route must have at least 2 stops'); + + const seqs = dto.stops.map(s => s.sequence); + if (new Set(seqs).size !== seqs.length) throw new ConflictException('Duplicate sequence numbers in stop list'); + + const stationIds = [...new Set(dto.stops.map(s => s.stationId))]; + const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } }); + if (stations.length !== stationIds.length) throw new BadRequestException('One or more station IDs not found'); + + return this.prisma.route.create({ + data: { + code: dto.code, + name: dto.name, + description: dto.description, + effectiveFrom: new Date(dto.effectiveFrom), + effectiveUntil: dto.effectiveUntil ? new Date(dto.effectiveUntil) : null, + stops: { + create: dto.stops.map(s => ({ + stationId: s.stationId, + sequence: s.sequence, + distanceKm: s.distanceKm, + })), + }, + }, + include: { stops: { include: { route: false }, orderBy: { sequence: 'asc' } } }, + }); + } + + async listRoutes(activeOnly = false) { + return this.prisma.route.findMany({ + where: activeOnly ? { active: true } : undefined, + include: { + stops: { orderBy: { sequence: 'asc' } }, + _count: { select: { stops: true } }, + }, + orderBy: { code: 'asc' }, + }); + } + + async getRoute(id: string) { + const route = await this.prisma.route.findUnique({ + where: { id }, + include: { + stops: { + orderBy: { sequence: 'asc' }, + include: { + route: false, + }, + }, + }, + }); + if (!route) throw new NotFoundException('Route not found'); + + // Enrich stops with station details + const stationIds = route.stops.map(s => s.stationId); + const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } }); + const stationMap = Object.fromEntries(stations.map(s => [s.id, s])); + + return { + ...route, + stops: route.stops.map(s => ({ ...s, station: stationMap[s.stationId] })), + }; + } + + async updateRoute(id: string, dto: UpdateRouteDto) { + const route = await this.prisma.route.findUnique({ where: { id } }); + if (!route) throw new NotFoundException('Route not found'); + return this.prisma.route.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + active: dto.active, + effectiveUntil: dto.effectiveUntil ? new Date(dto.effectiveUntil) : undefined, + }, + include: { stops: { orderBy: { sequence: 'asc' } } }, + }); + } + + // โ”€โ”€ Route Stops โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + async addStop(routeId: string, dto: AddRouteStopDto) { + const route = await this.prisma.route.findUnique({ where: { id: routeId } }); + if (!route) throw new NotFoundException('Route not found'); + + const station = await this.prisma.station.findUnique({ where: { id: dto.stationId } }); + if (!station) throw new NotFoundException(`Station ${dto.stationId} not found`); + + const existing = await this.prisma.routeStop.findUnique({ + where: { routeId_sequence: { routeId, sequence: dto.sequence } }, + }); + if (existing) throw new ConflictException(`Sequence ${dto.sequence} already exists on this route`); + + return this.prisma.routeStop.create({ + data: { routeId, stationId: dto.stationId, sequence: dto.sequence, distanceKm: dto.distanceKm }, + }); + } + + async removeStop(routeId: string, sequence: number) { + const stop = await this.prisma.routeStop.findUnique({ + where: { routeId_sequence: { routeId, sequence } }, + }); + if (!stop) throw new NotFoundException(`Stop at sequence ${sequence} not found on route`); + + const total = await this.prisma.routeStop.count({ where: { routeId } }); + if (total <= 2) throw new BadRequestException('A route must retain at least 2 stops'); + + await this.prisma.routeStop.delete({ where: { routeId_sequence: { routeId, sequence } } }); + return { deleted: true, sequence }; + } + + async getStops(routeId: string) { + const route = await this.prisma.route.findUnique({ where: { id: routeId } }); + if (!route) throw new NotFoundException('Route not found'); + + const stops = await this.prisma.routeStop.findMany({ + where: { routeId }, + orderBy: { sequence: 'asc' }, + }); + + const stationIds = stops.map(s => s.stationId); + const stations = await this.prisma.station.findMany({ where: { id: { in: stationIds } } }); + const stationMap = Object.fromEntries(stations.map(s => [s.id, s])); + + return stops.map(s => ({ ...s, station: stationMap[s.stationId] })); + } + + async getSchedulesForRoute(routeId: string) { + const route = await this.prisma.route.findUnique({ where: { id: routeId } }); + if (!route) throw new NotFoundException('Route not found'); + + return this.prisma.trainSchedule.findMany({ + where: { routeId }, + include: { train: true, originStation: true, destinationStation: true }, + orderBy: { departureAt: 'asc' }, + }); + } + + // โ”€โ”€ Used by SchedulesService โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + /** + * Copies RouteStop definitions into TripStopTime rows for a schedule. + * plannedTimes maps sequence โ†’ { arrivalAt?, departureAt? } for actual timing. + */ + async applyRouteToSchedule( + routeId: string, + scheduleId: string, + plannedTimes: Record, + ) { + const stops = await this.prisma.routeStop.findMany({ + where: { routeId }, + orderBy: { sequence: 'asc' }, + }); + if (stops.length === 0) throw new BadRequestException('Route has no stops defined'); + + await this.prisma.tripStopTime.deleteMany({ where: { scheduleId } }); + + await this.prisma.tripStopTime.createMany({ + data: stops.map(s => { + const times = plannedTimes[s.sequence] ?? {}; + return { + scheduleId, + stationId: s.stationId, + sequence: s.sequence, + plannedArrivalAt: times.plannedArrivalAt ? new Date(times.plannedArrivalAt) : null, + plannedDepartureAt: times.plannedDepartureAt ? new Date(times.plannedDepartureAt) : null, + }; + }), + }); + + const intermediateCount = Math.max(0, stops.length - 2); + await this.prisma.trainSchedule.update({ + where: { id: scheduleId }, + data: { stopsCount: intermediateCount }, + }); + + return this.prisma.tripStopTime.findMany({ + where: { scheduleId }, + include: { station: true }, + orderBy: { sequence: 'asc' }, + }); + } +} diff --git a/apps/edr-passenger-api/src/modules/schedules/schedules.controller.ts b/apps/edr-passenger-api/src/modules/schedules/schedules.controller.ts index 9f3972a49..07d0362af 100644 --- a/apps/edr-passenger-api/src/modules/schedules/schedules.controller.ts +++ b/apps/edr-passenger-api/src/modules/schedules/schedules.controller.ts @@ -1,21 +1,100 @@ -import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { Body, Controller, Get, Param, Patch, Post, Query, ParseIntPipe, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; import { SchedulesService } from './schedules.service'; -import { CreateTripDto, CreateFareRuleDto, UpdateTripStatusDto } from './schedules.dto'; +import { CreateScheduleDto, CreateFareRuleDto, UpdateScheduleStatusDto, UpdateStopTimeDto, ListSchedulesDto } from './schedules.dto'; import { JwtGuard } from '../../common/jwt.guard'; +import { TripStatus } from '@prisma/client'; @ApiTags('Schedule') -@Controller('schedule') +@Controller('schedules') export class SchedulesController { constructor(private service: SchedulesService) {} - @Post('trips') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Create trip' }) - createTrip(@Body() dto: CreateTripDto) { return this.service.createTrip(dto); } - @Get('trips/:id') @ApiOperation({ summary: 'Get trip details' }) - getTrip(@Param('id') id: string) { return this.service.getTrip(id); } - @Patch('trips/:id/status') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Update trip status' }) - updateStatus(@Param('id') id: string, @Body() dto: UpdateTripStatusDto) { return this.service.updateTripStatus(id, dto); } - @Post('fares') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Create fare rule' }) + + @Post() + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ + summary: 'Create a train schedule from a route template', + description: `Creates a schedule by referencing a Route (routeId). +Stops are automatically copied from the route's RouteStop definitions. +You supply the actual planned arrival/departure times per stop sequence. +Origin and destination are derived from the first and last route stop โ€” no need to specify them manually.`, + }) + @ApiResponse({ status: 201, description: 'Schedule created with stops copied from route template' }) + @ApiResponse({ status: 400, description: 'Invalid times, inactive route, or missing planned times for some stops' }) + @ApiResponse({ status: 404, description: 'Train or route not found' }) + createSchedule(@Body() dto: CreateScheduleDto) { return this.service.createSchedule(dto); } + + @Get() + @ApiOperation({ summary: 'List schedules with optional filters' }) + @ApiQuery({ name: 'date', required: false, example: '2026-06-15', description: 'Departure date (YYYY-MM-DD). Returns all schedules departing on this calendar day.' }) + @ApiQuery({ name: 'routeId', required: false, description: 'Filter by route UUID' }) + @ApiQuery({ name: 'trainId', required: false, description: 'Filter by train UUID' }) + @ApiQuery({ name: 'status', required: false, enum: TripStatus, description: 'Filter by schedule status' }) + @ApiResponse({ status: 200, description: 'Array of schedules ordered by departureAt, each with train, origin/destination, stops, and booking/assignment counts' }) + listSchedules( + @Query('date') date?: string, + @Query('routeId') routeId?: string, + @Query('trainId') trainId?: string, + @Query('status') status?: TripStatus, + ) { + return this.service.listSchedules({ date, routeId, trainId, status }); + } + + // Static routes before parameterised ones + @Post('fares') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Create a fare rule scoped to a schedule or route code' }) + @ApiResponse({ status: 201, description: 'Fare rule created' }) createFareRule(@Body() dto: CreateFareRuleDto) { return this.service.createFareRule(dto); } - @Get('fares/:tripId') @ApiOperation({ summary: 'Get fare for trip and class' }) - getFare(@Param('tripId') tripId: string, @Query('class') cls: string) { return this.service.getFare(tripId, cls ?? 'ECONOMY'); } + + @Get(':id') + @ApiOperation({ summary: 'Get schedule with train, coaches, seats, and stop timeline' }) + @ApiParam({ name: 'id', description: 'TrainSchedule UUID' }) + @ApiResponse({ status: 200, description: 'Full schedule detail including route stops with station info' }) + @ApiResponse({ status: 404, description: 'Schedule not found' }) + getSchedule(@Param('id') id: string) { return this.service.getSchedule(id); } + + @Patch(':id/status') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update schedule status (SCHEDULED โ†’ BOARDING โ†’ EN_ROUTE โ†’ ARRIVED)' }) + @ApiParam({ name: 'id', description: 'TrainSchedule UUID' }) + @ApiResponse({ status: 200, description: 'Status updated' }) + @ApiResponse({ status: 404, description: 'Schedule not found' }) + updateStatus(@Param('id') id: string, @Body() dto: UpdateScheduleStatusDto) { + return this.service.updateScheduleStatus(id, dto); + } + + // โ”€โ”€ Stop Times โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + @Get(':id/stops') + @ApiOperation({ summary: 'List all stops for a schedule ordered by sequence' }) + @ApiParam({ name: 'id', description: 'TrainSchedule UUID' }) + @ApiResponse({ status: 200, description: 'Ordered stop list with station details and planned/actual times' }) + @ApiResponse({ status: 404, description: 'Schedule not found' }) + getStops(@Param('id') id: string) { return this.service.getStops(id); } + + @Patch(':id/stops/:sequence') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update planned times or live status of a specific stop' }) + @ApiParam({ name: 'id', description: 'TrainSchedule UUID' }) + @ApiParam({ name: 'sequence', description: 'Stop sequence number' }) + @ApiResponse({ status: 200, description: 'Stop updated' }) + @ApiResponse({ status: 404, description: 'Stop not found on schedule' }) + updateStop( + @Param('id') id: string, + @Param('sequence', ParseIntPipe) sequence: number, + @Body() dto: UpdateStopTimeDto, + ) { return this.service.updateStop(id, sequence, dto); } + + // โ”€โ”€ Fares โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + @Get(':scheduleId/fares') + @ApiOperation({ summary: 'Get applicable fare for a schedule and seat class' }) + @ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' }) + @ApiQuery({ name: 'class', required: false, description: 'Seat class name: "Economy Regular" | "Economy Bed" | "VIP Bed". Defaults to Economy Regular.' }) + @ApiResponse({ status: 200, description: 'Fare rule or default fare' }) + @ApiResponse({ status: 404, description: 'Schedule not found' }) + getFare(@Param('scheduleId') scheduleId: string, @Query('class') cls: string) { + return this.service.getFare(scheduleId, cls ?? 'Economy Regular'); + } } diff --git a/apps/edr-passenger-api/src/modules/schedules/schedules.dto.ts b/apps/edr-passenger-api/src/modules/schedules/schedules.dto.ts index 80638d5ae..8ff79c527 100644 --- a/apps/edr-passenger-api/src/modules/schedules/schedules.dto.ts +++ b/apps/edr-passenger-api/src/modules/schedules/schedules.dto.ts @@ -1,25 +1,68 @@ -import { IsString, IsDateString, IsInt, IsOptional, IsEnum } from 'class-validator'; +import { IsString, IsDateString, IsInt, IsOptional, IsEnum, IsArray, ValidateNested, IsObject, Min } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ServiceClass } from '@prisma/client'; +import { Type } from 'class-transformer'; +import { TripStatus, StopStatus } from '@prisma/client'; -export class CreateTripDto { - @ApiProperty() @IsString() serviceId: string; - @ApiProperty() @IsString() originStationId: string; - @ApiProperty() @IsString() destinationStationId: string; - @ApiProperty({ example: '2026-05-11T08:30:00Z' }) @IsDateString() departureAt: string; - @ApiProperty({ example: '2026-05-11T20:00:00Z' }) @IsDateString() arrivalAt: string; - @ApiPropertyOptional() @IsOptional() @IsInt() stopsCount?: number; +export class PlannedStopTimeDto { + @ApiProperty({ example: 1, description: 'Route stop sequence number this timing applies to' }) @IsInt() @Min(1) sequence: number; + @ApiPropertyOptional({ example: '2026-06-15T09:30:00Z', description: 'Planned arrival at this stop (omit for first stop)' }) @IsOptional() @IsDateString() plannedArrivalAt?: string; + @ApiPropertyOptional({ example: '2026-06-15T09:45:00Z', description: 'Planned departure from this stop (omit for last stop)' }) @IsOptional() @IsDateString() plannedDepartureAt?: string; +} + +export class CreateScheduleDto { + @ApiProperty({ example: 'train-uuid', description: 'Train UUID' }) @IsString() trainId: string; + + @ApiProperty({ example: 'route-uuid', description: 'Route UUID โ€” stops are copied from the route template. Origin and destination are derived from the first and last route stop.' }) + @IsString() routeId: string; + + @ApiProperty({ example: '2026-06-15T08:00:00Z', description: 'Scheduled departure from the first stop (origin)' }) @IsDateString() departureAt: string; + @ApiProperty({ example: '2026-06-15T20:00:00Z', description: 'Scheduled arrival at the last stop (destination)' }) @IsDateString() arrivalAt: string; + + @ApiProperty({ + type: [PlannedStopTimeDto], + description: 'Planned arrival/departure times per stop sequence. Must cover all stops defined on the route.', + example: [ + { sequence: 1, plannedDepartureAt: '2026-06-15T08:00:00Z' }, + { sequence: 2, plannedArrivalAt: '2026-06-15T09:30:00Z', plannedDepartureAt: '2026-06-15T09:45:00Z' }, + { sequence: 3, plannedArrivalAt: '2026-06-15T11:30:00Z', plannedDepartureAt: '2026-06-15T11:45:00Z' }, + { sequence: 4, plannedArrivalAt: '2026-06-15T15:00:00Z', plannedDepartureAt: '2026-06-15T15:20:00Z' }, + { sequence: 5, plannedArrivalAt: '2026-06-15T18:00:00Z', plannedDepartureAt: '2026-06-15T18:10:00Z' }, + { sequence: 6, plannedArrivalAt: '2026-06-15T20:00:00Z' }, + ], + }) + @IsArray() @ValidateNested({ each: true }) @Type(() => PlannedStopTimeDto) + plannedTimes: PlannedStopTimeDto[]; +} + +export class UpdateStopTimeDto { + @ApiPropertyOptional({ example: '2026-06-15T09:30:00Z' }) @IsOptional() @IsDateString() plannedArrivalAt?: string; + @ApiPropertyOptional({ example: '2026-06-15T09:45:00Z' }) @IsOptional() @IsDateString() plannedDepartureAt?: string; + @ApiPropertyOptional({ enum: StopStatus, example: StopStatus.UPCOMING }) @IsOptional() @IsEnum(StopStatus) status?: StopStatus; } export class CreateFareRuleDto { - @ApiPropertyOptional() @IsOptional() @IsString() tripId?: string; - @ApiPropertyOptional() @IsOptional() @IsString() route?: string; - @ApiProperty({ enum: ServiceClass }) @IsEnum(ServiceClass) serviceClass: ServiceClass; - @ApiProperty({ example: 45000 }) @IsInt() baseFareMinor: number; + @ApiPropertyOptional({ example: 'schedule-uuid', description: 'Scope fare rule to a specific schedule' }) @IsOptional() @IsString() scheduleId?: string; + @ApiPropertyOptional({ example: 'ADD-DJI', description: 'Scope fare rule to a route code (e.g. ADD-DJI)' }) @IsOptional() @IsString() route?: string; + @ApiProperty({ example: 'seat-class-uuid', description: 'Seat class UUID' }) @IsString() seatClassId: string; + @ApiProperty({ example: 45000, description: 'Base fare in minor currency units (ETB cents)' }) @IsInt() baseFareMinor: number; @ApiProperty({ example: '2026-01-01T00:00:00Z' }) @IsDateString() validFrom: string; - @ApiPropertyOptional() @IsOptional() @IsDateString() validUntil?: string; + @ApiPropertyOptional({ example: '2026-12-31T23:59:59Z' }) @IsOptional() @IsDateString() validUntil?: string; } -export class UpdateTripStatusDto { - @ApiProperty({ example: 'EN_ROUTE' }) @IsString() status: string; +export class ListSchedulesDto { + @ApiPropertyOptional({ example: '2026-06-15', description: 'Filter by departure date (YYYY-MM-DD). Returns all schedules departing on this calendar day.' }) + @IsOptional() @IsDateString() date?: string; + + @ApiPropertyOptional({ example: 'route-uuid', description: 'Filter by route UUID' }) + @IsOptional() @IsString() routeId?: string; + + @ApiPropertyOptional({ example: 'train-uuid', description: 'Filter by train UUID' }) + @IsOptional() @IsString() trainId?: string; + + @ApiPropertyOptional({ enum: TripStatus, example: TripStatus.SCHEDULED, description: 'Filter by schedule status' }) + @IsOptional() @IsEnum(TripStatus) status?: TripStatus; +} + +export class UpdateScheduleStatusDto { + @ApiProperty({ enum: TripStatus, example: TripStatus.EN_ROUTE }) @IsEnum(TripStatus) status: TripStatus; } diff --git a/apps/edr-passenger-api/src/modules/schedules/schedules.module.ts b/apps/edr-passenger-api/src/modules/schedules/schedules.module.ts index 41e48bea6..9df073e74 100644 --- a/apps/edr-passenger-api/src/modules/schedules/schedules.module.ts +++ b/apps/edr-passenger-api/src/modules/schedules/schedules.module.ts @@ -1,6 +1,12 @@ import { Module } from '@nestjs/common'; import { SchedulesController } from './schedules.controller'; import { SchedulesService } from './schedules.service'; +import { RoutesController } from './routes.controller'; +import { RoutesService } from './routes.service'; -@Module({ controllers: [SchedulesController], providers: [SchedulesService] }) +@Module({ + controllers: [RoutesController, SchedulesController], + providers: [RoutesService, SchedulesService], + exports: [RoutesService, SchedulesService], +}) export class SchedulesModule {} diff --git a/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts b/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts index a97b54fd6..e962be741 100644 --- a/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts +++ b/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts @@ -1,39 +1,174 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; -import { CreateTripDto, CreateFareRuleDto, UpdateTripStatusDto } from './schedules.dto'; +import { RoutesService } from './routes.service'; +import { CreateScheduleDto, CreateFareRuleDto, UpdateScheduleStatusDto, UpdateStopTimeDto, ListSchedulesDto } from './schedules.dto'; @Injectable() export class SchedulesService { - constructor(private prisma: PrismaService) {} + constructor( + private prisma: PrismaService, + private routesService: RoutesService, + ) {} - async createTrip(dto: CreateTripDto) { - const dep = new Date(dto.departureAt), arr = new Date(dto.arrivalAt); - return this.prisma.trip.create({ - data: { serviceId: dto.serviceId, originStationId: dto.originStationId, destinationStationId: dto.destinationStationId, departureAt: dep, arrivalAt: arr, durationMinutes: Math.round((arr.getTime() - dep.getTime()) / 60000), stopsCount: dto.stopsCount ?? 0 }, - include: { service: true, originStation: true, destinationStation: true }, + // โ”€โ”€ Schedule CRUD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + async listSchedules(dto: ListSchedulesDto) { + const where: any = {}; + + if (dto.date) { + const date = new Date(dto.date); + const nextDay = new Date(date.getTime() + 86_400_000); + where.departureAt = { gte: date, lt: nextDay }; + } + if (dto.routeId) where.routeId = dto.routeId; + if (dto.trainId) where.trainId = dto.trainId; + if (dto.status) where.status = dto.status; + + return this.prisma.trainSchedule.findMany({ + where, + include: { + train: true, + originStation: true, + destinationStation: true, + stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, + _count: { select: { coachAssignments: true, bookings: true } }, + }, + orderBy: { departureAt: 'asc' }, }); } - async getTrip(id: string) { - const trip = await this.prisma.trip.findUnique({ where: { id }, include: { service: true, originStation: true, destinationStation: true, coaches: { include: { seats: true } }, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }); - if (!trip) throw new NotFoundException('Trip not found'); - return trip; + async createSchedule(dto: CreateScheduleDto) { + const dep = new Date(dto.departureAt); + const arr = new Date(dto.arrivalAt); + if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt'); + + // Validate route exists and has stops + const route = await this.prisma.route.findUnique({ + where: { id: dto.routeId }, + include: { stops: { orderBy: { sequence: 'asc' } } }, + }); + if (!route) throw new NotFoundException('Route not found'); + if (!route.active) throw new BadRequestException('Route is not active'); + if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops'); + + // Validate all route stop sequences are covered by plannedTimes + const providedSeqs = new Set(dto.plannedTimes.map(t => t.sequence)); + const missingSeqs = route.stops.map(s => s.sequence).filter(seq => !providedSeqs.has(seq)); + if (missingSeqs.length > 0) { + throw new BadRequestException(`Missing planned times for stop sequences: ${missingSeqs.join(', ')}`); + } + + // Derive origin and destination from first and last route stop + const firstStop = route.stops[0]; + const lastStop = route.stops[route.stops.length - 1]; + + const schedule = await this.prisma.trainSchedule.create({ + data: { + trainId: dto.trainId, + routeId: dto.routeId, + originStationId: firstStop.stationId, + destinationStationId: lastStop.stationId, + departureAt: dep, + arrivalAt: arr, + durationMinutes: Math.round((arr.getTime() - dep.getTime()) / 60_000), + stopsCount: Math.max(0, route.stops.length - 2), + }, + include: { train: true, originStation: true, destinationStation: true }, + }); + + // Copy route stops into TripStopTime with the provided planned times + const plannedTimesMap = Object.fromEntries( + dto.plannedTimes.map(t => [t.sequence, t]), + ); + await this.routesService.applyRouteToSchedule(dto.routeId, schedule.id, plannedTimesMap); + + return this.getSchedule(schedule.id); } - updateTripStatus(id: string, dto: UpdateTripStatusDto) { return this.prisma.trip.update({ where: { id }, data: { status: dto.status as any } }); } + async getSchedule(id: string) { + const schedule = await this.prisma.trainSchedule.findUnique({ + where: { id }, + include: { + train: true, + originStation: true, + destinationStation: true, + coachAssignments: { + include: { coach: { include: { seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] }, seatClass: true } } }, + orderBy: { positionNumber: 'asc' }, + }, + stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, + }, + }); + if (!schedule) throw new NotFoundException('Schedule not found'); + return schedule; + } + + updateScheduleStatus(id: string, dto: UpdateScheduleStatusDto) { + return this.prisma.trainSchedule.update({ where: { id }, data: { status: dto.status } }); + } + + // โ”€โ”€ Stop Times (per-schedule overrides) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + getStops(scheduleId: string) { + return this.prisma.tripStopTime.findMany({ + where: { scheduleId }, + include: { station: true }, + orderBy: { sequence: 'asc' }, + }); + } + + async updateStop(scheduleId: string, sequence: number, dto: UpdateStopTimeDto) { + const stop = await this.prisma.tripStopTime.findUnique({ + where: { scheduleId_sequence: { scheduleId, sequence } }, + }); + if (!stop) throw new NotFoundException(`Stop at sequence ${sequence} not found on schedule`); + + return this.prisma.tripStopTime.update({ + where: { scheduleId_sequence: { scheduleId, sequence } }, + data: { + plannedArrivalAt: dto.plannedArrivalAt ? new Date(dto.plannedArrivalAt) : undefined, + plannedDepartureAt: dto.plannedDepartureAt ? new Date(dto.plannedDepartureAt) : undefined, + status: dto.status, + }, + include: { station: true }, + }); + } + + // โ”€โ”€ Fare Rules โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ createFareRule(dto: CreateFareRuleDto) { - return this.prisma.fareRule.create({ data: { ...dto, validFrom: new Date(dto.validFrom), validUntil: dto.validUntil ? new Date(dto.validUntil) : null } }); + const { validFrom, validUntil, scheduleId, ...rest } = dto; + return this.prisma.fareRule.create({ + data: { + ...rest, + tripId: scheduleId, + validFrom: new Date(validFrom), + validUntil: validUntil ? new Date(validUntil) : null, + }, + }); } - async getFare(tripId: string, serviceClass: string) { - const trip = await this.prisma.trip.findUnique({ where: { id: tripId }, include: { originStation: true, destinationStation: true } }); - if (!trip) throw new NotFoundException('Trip not found'); - const route = `${trip.originStation.code}-${trip.destinationStation.code}`; + async getFare(scheduleId: string, seatClassName: string) { + const schedule = await this.prisma.trainSchedule.findUnique({ + where: { id: scheduleId }, + include: { originStation: true, destinationStation: true }, + }); + if (!schedule) throw new NotFoundException('Schedule not found'); + + const route = `${schedule.originStation.code}-${schedule.destinationStation.code}`; + const seatClass = await this.prisma.seatClass.findFirst({ where: { name: seatClassName } }); + const now = new Date(); + const rule = await this.prisma.fareRule.findFirst({ - where: { serviceClass: serviceClass as any, validFrom: { lte: new Date() }, OR: [{ tripId }, { route }, { tripId: null, route: null }], AND: [{ OR: [{ validUntil: null }, { validUntil: { gte: new Date() } }] }] }, + where: { + seatClassId: seatClass?.id, + validFrom: { lte: now }, + OR: [{ tripId: scheduleId }, { route }, { tripId: null, route: null }], + AND: [{ OR: [{ validUntil: null }, { validUntil: { gte: now } }] }], + }, orderBy: { validFrom: 'desc' }, }); - return rule ?? { baseFareMinor: 45000, currency: 'ETB', serviceClass }; + + return rule ?? { baseFareMinor: 45000, currency: 'ETB', seatClassName }; } } diff --git a/apps/edr-passenger-api/src/modules/search/search.controller.ts b/apps/edr-passenger-api/src/modules/search/search.controller.ts index 9409873ed..5dbbd7759 100644 --- a/apps/edr-passenger-api/src/modules/search/search.controller.ts +++ b/apps/edr-passenger-api/src/modules/search/search.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Post } from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { SearchService } from './search.service'; import { SearchTripsDto, FareQuoteDto } from './search.dto'; @@ -7,6 +7,40 @@ import { SearchTripsDto, FareQuoteDto } from './search.dto'; @Controller('search') export class SearchController { constructor(private service: SearchService) {} - @Post() @ApiOperation({ summary: 'Search trips' }) searchTrips(@Body() dto: SearchTripsDto) { return this.service.searchTrips(dto); } - @Post('fare-quote')@ApiOperation({ summary: 'Get fare quote' }) getFareQuote(@Body() dto: FareQuoteDto) { return this.service.getFareQuote(dto); } + + @Post() + @ApiOperation({ + summary: 'Search schedules by any originโ€“destination stop pair', + description: `Finds all train schedules where both origin and destination appear as stops (not just terminals). + +Example: A train running Aโ†’Bโ†’Cโ†’D will appear in results for Aโ†’B, Aโ†’C, Aโ†’D, Bโ†’C, Bโ†’D, and Cโ†’D searches. + +Availability is computed per seat per segment โ€” a seat booked Aโ†’B is still shown as available for Bโ†’D. + +Returns departure/arrival times for the requested leg, the full stop list, and per-class seat counts.` + }) + @ApiResponse({ status: 200, description: 'Matching schedules with segment-accurate seat availability per class' }) + searchTrips(@Body() dto: SearchTripsDto) { + return this.service.searchTrips(dto); + } + + @Post('fare-quote') + @ApiOperation({ + summary: 'Get fare quote for a specific schedule leg', + description: `Calculates fare for the requested originโ†’destination leg on a schedule. + +Pricing rules (in priority order): +1. Schedule-scoped FareRule (tripId = scheduleId) +2. Segment route FareRule (e.g. ADD-DRE) +3. Full-route FareRule (e.g. ADD-DJI) +4. Default hardcoded fare + +Age-based pricing: first child (age < 5) travels free, subsequent children pay full fare. +Supports multi-currency display (ETB, DJF, USD).` + }) + @ApiResponse({ status: 200, description: 'Fare breakdown with adult/child pricing, discounts, taxes, and currency conversion' }) + @ApiResponse({ status: 404, description: 'Schedule not found or origin/destination not on schedule' }) + getFareQuote(@Body() dto: FareQuoteDto) { + return this.service.getFareQuote(dto); + } } diff --git a/apps/edr-passenger-api/src/modules/search/search.dto.ts b/apps/edr-passenger-api/src/modules/search/search.dto.ts index e4e3d2cae..571b2a354 100644 --- a/apps/edr-passenger-api/src/modules/search/search.dto.ts +++ b/apps/edr-passenger-api/src/modules/search/search.dto.ts @@ -1,18 +1,50 @@ -import { IsString, IsDateString, IsInt, IsOptional, Min } from 'class-validator'; +import { IsString, IsDateString, IsInt, IsOptional, Min, IsEnum } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; +import { Currency } from '@prisma/client'; export class SearchTripsDto { - @ApiProperty({ example: 'st_ADD' }) @IsString() originStationId: string; - @ApiProperty({ example: 'st_DJI' }) @IsString() destinationStationId: string; - @ApiProperty({ example: '2026-05-11' }) @IsDateString() date: string; - @ApiPropertyOptional({ example: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) passengers?: number; + @ApiProperty({ example: 'station-uuid', description: 'Origin station UUID โ€” any intermediate stop is valid, not just the terminal' }) + @IsString() originStationId: string; + + @ApiProperty({ example: 'station-uuid', description: 'Destination station UUID โ€” must appear after origin in the stop sequence' }) + @IsString() destinationStationId: string; + + @ApiProperty({ example: '2026-06-15', description: 'Departure date (YYYY-MM-DD)' }) + @IsDateString() date: string; + + @ApiProperty({ example: 2, description: 'Number of adult passengers (age โ‰ฅ 5)' }) + @Type(() => Number) @IsInt() @Min(1) adultCount: number; + + @ApiPropertyOptional({ example: 1, description: 'Number of child passengers (age < 5). First child travels free.' }) + @IsOptional() @Type(() => Number) @IsInt() @Min(0) childCount?: number; } export class FareQuoteDto { - @ApiProperty() @IsString() tripId: string; - @ApiProperty({ example: 'ECONOMY' }) @IsString() serviceClass: string; - @ApiPropertyOptional({ example: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) passengerCount?: number; - @ApiPropertyOptional({ example: 'WEEKEND15' }) @IsOptional() @IsString() promoCode?: string; - @ApiPropertyOptional({ example: 450 }) @IsOptional() @Type(() => Number) @IsInt() loyaltyRedemptionPoints?: number; + @ApiProperty({ example: 'schedule-uuid', description: 'TrainSchedule UUID from search results' }) + @IsString() scheduleId: string; + + @ApiProperty({ example: 'station-uuid', description: 'Origin station UUID (must be a stop on the schedule)' }) + @IsString() originStationId: string; + + @ApiProperty({ example: 'station-uuid', description: 'Destination station UUID (must come after origin in stop sequence)' }) + @IsString() destinationStationId: string; + + @ApiProperty({ example: 'Economy Regular', description: 'Seat class name: "Economy Regular" | "Economy Bed" | "VIP Bed"' }) + @IsString() seatClassName: string; + + @ApiProperty({ example: 2, description: 'Number of adult passengers' }) + @Type(() => Number) @IsInt() @Min(1) adultCount: number; + + @ApiPropertyOptional({ example: 1 }) + @IsOptional() @Type(() => Number) @IsInt() @Min(0) childCount?: number; + + @ApiPropertyOptional({ example: 'WEEKEND15' }) + @IsOptional() @IsString() promoCode?: string; + + @ApiPropertyOptional({ example: 450, description: 'Loyalty points to redeem (10 points = 1 ETB minor unit)' }) + @IsOptional() @Type(() => Number) @IsInt() loyaltyRedemptionPoints?: number; + + @ApiPropertyOptional({ example: 'ETB', enum: Currency }) + @IsOptional() @IsEnum(Currency) displayCurrency?: Currency; } diff --git a/apps/edr-passenger-api/src/modules/search/search.module.ts b/apps/edr-passenger-api/src/modules/search/search.module.ts index e156d3cde..5228590fb 100644 --- a/apps/edr-passenger-api/src/modules/search/search.module.ts +++ b/apps/edr-passenger-api/src/modules/search/search.module.ts @@ -1,6 +1,12 @@ import { Module } from '@nestjs/common'; import { SearchController } from './search.controller'; import { SearchService } from './search.service'; +import { CurrencyModule } from '../currency/currency.module'; -@Module({ controllers: [SearchController], providers: [SearchService], exports: [SearchService] }) +@Module({ + imports: [CurrencyModule], + controllers: [SearchController], + providers: [SearchService], + exports: [SearchService] +}) export class SearchModule {} diff --git a/apps/edr-passenger-api/src/modules/search/search.service.ts b/apps/edr-passenger-api/src/modules/search/search.service.ts index 046bdc70d..062736d2e 100644 --- a/apps/edr-passenger-api/src/modules/search/search.service.ts +++ b/apps/edr-passenger-api/src/modules/search/search.service.ts @@ -1,50 +1,269 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; import { SearchTripsDto, FareQuoteDto } from './search.dto'; +import { CurrencyService } from '../currency/currency.service'; +import { Currency } from '@prisma/client'; const POINTS_TO_MINOR = 10; @Injectable() export class SearchService { - constructor(private prisma: PrismaService) {} + constructor( + private prisma: PrismaService, + private currencyService: CurrencyService, + ) {} async searchTrips(dto: SearchTripsDto) { - const date = new Date(dto.date), nextDay = new Date(date.getTime() + 86400000); - const trips = await this.prisma.trip.findMany({ - where: { originStationId: dto.originStationId, destinationStationId: dto.destinationStationId, departureAt: { gte: date, lt: nextDay }, status: { in: ['SCHEDULED', 'BOARDING'] } }, - include: { service: true, originStation: true, destinationStation: true, coaches: { include: { seats: true } } }, - }); - return trips.map((trip) => { - const seatsByClass = (cls: string) => trip.coaches.filter((c) => c.serviceClass === cls).flatMap((c) => c.seats); - const avail = (cls: string) => seatsByClass(cls).filter((s) => s.status === 'AVAILABLE').length; - return { - id: trip.id, - number: trip.service.number, - origin: { id: trip.originStation.id, code: trip.originStation.code, name: trip.originStation.name, city: trip.originStation.city }, - destination: { id: trip.destinationStation.id, code: trip.destinationStation.code, name: trip.destinationStation.name, city: trip.destinationStation.city }, - departureAt: trip.departureAt, arrivalAt: trip.arrivalAt, status: trip.status, - availability: { ECONOMY: avail('ECONOMY'), BUSINESS: avail('BUSINESS'), FIRST: avail('FIRST') }, - fares: { ECONOMY: this.defaultFare('ECONOMY') / 100, BUSINESS: this.defaultFare('BUSINESS') / 100, FIRST: this.defaultFare('FIRST') / 100 }, - }; + const date = new Date(dto.date); + const nextDay = new Date(date.getTime() + 86_400_000); + const totalPassengers = dto.adultCount + (dto.childCount ?? 0); + + // Find all schedules that have BOTH origin and destination as stops + // (not just terminal-to-terminal) and depart on the requested date + const schedules = await this.prisma.trainSchedule.findMany({ + where: { + status: { in: ['SCHEDULED', 'BOARDING'] }, + departureAt: { gte: date, lt: nextDay }, + stopTimes: { some: { stationId: dto.originStationId } }, + }, + include: { + train: true, + originStation: true, + destinationStation: true, + stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, + coachAssignments: { + include: { coach: { include: { seats: true, seatClass: true } } }, + }, + }, }); + + const results = []; + + for (const schedule of schedules) { + const originStop = schedule.stopTimes.find(s => s.stationId === dto.originStationId); + const destStop = schedule.stopTimes.find(s => s.stationId === dto.destinationStationId); + + // Both stops must exist and origin must come before destination + if (!originStop || !destStop || originStop.sequence >= destStop.sequence) continue; + + // Compute per-seat availability for the requested segment range + // A seat is available if no active booking/hold overlaps [originSeq, destSeq) + const availabilityByClass: Record = {}; + + for (const assignment of schedule.coachAssignments) { + const className = assignment.coach.seatClass.name; + if (!availabilityByClass[className]) availabilityByClass[className] = 0; + + for (const seat of assignment.coach.seats) { + if (seat.status === 'BLOCKED') continue; + const free = await this.isSeatFreeForSegment( + schedule.id, seat.id, + originStop.sequence, destStop.sequence, + ); + if (free) availabilityByClass[className]++; + } + } + + // Departure/arrival times for the requested leg (not the full schedule) + const legDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt; + const legArrivalAt = destStop.plannedArrivalAt ?? schedule.arrivalAt; + + results.push({ + scheduleId: schedule.id, + trainNumber: schedule.train.number, + trainName: schedule.train.name, + origin: { + id: originStop.stationId, + code: originStop.station.code, + name: originStop.station.name, + city: originStop.station.city, + sequence: originStop.sequence, + }, + destination: { + id: destStop.stationId, + code: destStop.station.code, + name: destStop.station.name, + city: destStop.station.city, + sequence: destStop.sequence, + }, + departureAt: legDepartureAt, + arrivalAt: legArrivalAt, + durationMinutes: Math.round( + (new Date(legArrivalAt).getTime() - new Date(legDepartureAt).getTime()) / 60_000, + ), + status: schedule.status, + // Only return stops within the requested leg (origin โ†’ destination inclusive) + stops: schedule.stopTimes + .filter(st => st.sequence >= originStop.sequence && st.sequence <= destStop.sequence) + .map(st => ({ + stationId: st.stationId, + stationName: st.station.name, + sequence: st.sequence, + plannedArrivalAt: st.plannedArrivalAt, + plannedDepartureAt: st.plannedDepartureAt, + })), + availabilityByClass, + hasAvailability: Object.values(availabilityByClass).some(n => n >= totalPassengers), + }); + } + + return results; } async getFareQuote(dto: FareQuoteDto) { - const trip = await this.prisma.trip.findUnique({ where: { id: dto.tripId } }); - if (!trip) throw new NotFoundException('Trip not found'); - const count = dto.passengerCount ?? 1; - const baseFareMinor = this.defaultFare(dto.serviceClass) * count; + const schedule = await this.prisma.trainSchedule.findUnique({ + where: { id: dto.scheduleId }, + include: { + originStation: true, + destinationStation: true, + stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, + }, + }); + if (!schedule) throw new NotFoundException('Schedule not found'); + + const originStop = schedule.stopTimes.find(s => s.stationId === dto.originStationId); + const destStop = schedule.stopTimes.find(s => s.stationId === dto.destinationStationId); + if (!originStop || !destStop || originStop.sequence >= destStop.sequence) { + throw new NotFoundException('Origin or destination not found on this schedule'); + } + + const seatClass = await this.prisma.seatClass.findFirst({ where: { name: dto.seatClassName } }); + + // Look up fare rule: prefer schedule-scoped, then segment route, then global + const segmentRoute = `${originStop.station.code}-${destStop.station.code}`; + const fullRoute = `${schedule.originStation.code}-${schedule.destinationStation.code}`; + const now = new Date(); + + const fareRule = await this.prisma.fareRule.findFirst({ + where: { + seatClassId: seatClass?.id, + validFrom: { lte: now }, + OR: [ + { validUntil: null }, + { validUntil: { gte: now } }, + ], + }, + orderBy: [ + // Most specific first: schedule-scoped > segment route > full route > global + { tripId: 'desc' }, + { validFrom: 'desc' }, + ], + }); + + const baseFareMinor = fareRule?.baseFareMinor ?? this.defaultFare(dto.seatClassName); + + const adultCount = dto.adultCount; + const childCount = dto.childCount ?? 0; + const adultFareMinor = baseFareMinor * adultCount; + const paidChildrenCount = Math.max(0, childCount - 1); + const childFareMinor = baseFareMinor * paidChildrenCount; + const totalBaseFareMinor = adultFareMinor + childFareMinor; + let discountMinor = 0; if (dto.promoCode) { const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } }); - if (promo?.active && promo.validUntil > new Date()) discountMinor = promo.percentOff ? Math.round(baseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0); + if (promo?.active && promo.validUntil > now) { + discountMinor = promo.percentOff + ? Math.round(totalBaseFareMinor * promo.percentOff / 100) + : (promo.amountOffMinor ?? 0); + } } + const loyaltyMinor = (dto.loyaltyRedemptionPoints ?? 0) * POINTS_TO_MINOR; - const taxesMinor = Math.round(baseFareMinor * 0.05); - return { tripId: dto.tripId, serviceClass: dto.serviceClass, passengerCount: count, baseFareMinor, discountMinor, loyaltyRedemptionMinor: loyaltyMinor, taxesFeesMinor: taxesMinor, totalMinor: Math.max(0, baseFareMinor - discountMinor - loyaltyMinor + taxesMinor), currency: 'ETB' }; + const taxesMinor = Math.round(totalBaseFareMinor * 0.05); + const totalMinor = Math.max(0, totalBaseFareMinor - discountMinor - loyaltyMinor + taxesMinor); + + const displayCurrency = dto.displayCurrency ?? Currency.ETB; + const displayTotalMinor = displayCurrency !== Currency.ETB + ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency) + : totalMinor; + + return { + scheduleId: dto.scheduleId, + originStationId: dto.originStationId, + destinationStationId: dto.destinationStationId, + segmentRoute, + seatClassName: dto.seatClassName, + adultCount, childCount, + baseFareMinor, adultFareMinor, childFareMinor, + freeChildrenCount: Math.min(childCount, 1), + paidChildrenCount, totalBaseFareMinor, + discountMinor, loyaltyRedemptionMinor: loyaltyMinor, + taxesFeesMinor: taxesMinor, totalMinor, + currency: 'ETB', displayCurrency, displayTotalMinor, + }; } - private defaultFare(serviceClass: string): number { - return ({ ECONOMY: 45000, BUSINESS: 90000, FIRST: 135000 } as any)[serviceClass] ?? 45000; + /** + * Returns true if the seat has no active hold or confirmed booking + * whose segment range overlaps [fromSeq, toSeq). + * Overlap condition: existingFrom < toSeq AND fromSeq < existingTo + */ + private async isSeatFreeForSegment( + scheduleId: string, + seatId: string, + fromSeq: number, + toSeq: number, + ): Promise { + // Check active holds that include this seat on this schedule + const holds = await this.prisma.seatHold.findMany({ + where: { scheduleId, seatIds: { has: seatId }, expiresAt: { gt: new Date() } }, + }); + + for (const hold of holds) { + // Resolve hold segment range from its stored origin/destination via JourneySegment + // For holds we use the stop sequences stored on the hold's origin/destination + // Since SeatHold doesn't store sequences directly, we check JourneySegments + // that reference this seat on this schedule with PENDING_PAYMENT status + const holdSegs = await this.prisma.journeySegment.findMany({ + where: { scheduleId, seatId }, + include: { + journey: true, + schedule: { include: { stopTimes: true } }, + }, + }); + + for (const js of holdSegs) { + const depSeq = js.schedule.stopTimes.find(s => s.stationId === js.departureStationId)?.sequence; + const arrSeq = js.schedule.stopTimes.find(s => s.stationId === js.arrivalStationId)?.sequence; + if (depSeq !== undefined && arrSeq !== undefined) { + if (depSeq < toSeq && fromSeq < arrSeq) return false; + } + } + + // If no journey segments yet (hold just created), treat the whole hold as blocking + if (holdSegs.length === 0) return false; + } + + // Check confirmed/pending bookings via JourneySegment + const bookedSegments = await this.prisma.journeySegment.findMany({ + where: { + scheduleId, + seatId, + journey: { status: { in: ['CONFIRMED', 'PENDING_PAYMENT'] } }, + }, + include: { + schedule: { include: { stopTimes: true } }, + }, + }); + + for (const js of bookedSegments) { + const depSeq = js.schedule.stopTimes.find(s => s.stationId === js.departureStationId)?.sequence; + const arrSeq = js.schedule.stopTimes.find(s => s.stationId === js.arrivalStationId)?.sequence; + if (depSeq !== undefined && arrSeq !== undefined) { + if (depSeq < toSeq && fromSeq < arrSeq) return false; + } + } + + return true; + } + + private defaultFare(seatClassName: string): number { + const fares: Record = { + 'Economy Regular': 45000, + 'Economy Bed': 65000, + 'VIP Bed': 95000, + }; + return fares[seatClassName] ?? 45000; } } diff --git a/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.controller.ts b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.controller.ts new file mode 100644 index 000000000..945834ac0 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.controller.ts @@ -0,0 +1,40 @@ +import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiResponse, ApiBody } from '@nestjs/swagger'; +import { SeatClassesService } from './seat-classes.service'; +import { CreateSeatClassDto, UpdateSeatClassDto } from './seat-classes.dto'; +import { JwtGuard } from '../../common/jwt.guard'; + +@ApiTags('Seat Classes') +@Controller('seat-classes') +export class SeatClassesController { + constructor(private service: SeatClassesService) {} + + @Get() + @ApiOperation({ summary: 'List all seat classes' }) + @ApiResponse({ status: 200, description: 'Returns all seat classes with their coaches' }) + listSeatClasses() { return this.service.listSeatClasses(); } + + @Get(':id') + @ApiOperation({ summary: 'Get a seat class by ID' }) + @ApiParam({ name: 'id', description: 'Seat class UUID' }) + @ApiResponse({ status: 200, description: 'Returns seat class with its coaches' }) + @ApiResponse({ status: 404, description: 'Seat class not found' }) + getSeatClass(@Param('id') id: string) { return this.service.getSeatClass(id); } + + @Post() + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Create a seat class' }) + @ApiBody({ type: CreateSeatClassDto }) + @ApiResponse({ status: 201, description: 'Seat class created' }) + @ApiResponse({ status: 409, description: 'Seat class name already exists' }) + createSeatClass(@Body() dto: CreateSeatClassDto) { return this.service.createSeatClass(dto); } + + @Patch(':id') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Update a seat class' }) + @ApiParam({ name: 'id', description: 'Seat class UUID' }) + @ApiBody({ type: UpdateSeatClassDto }) + @ApiResponse({ status: 200, description: 'Seat class updated' }) + @ApiResponse({ status: 404, description: 'Seat class not found' }) + updateSeatClass(@Param('id') id: string, @Body() dto: UpdateSeatClassDto) { return this.service.updateSeatClass(id, dto); } +} diff --git a/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.dto.ts b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.dto.ts new file mode 100644 index 000000000..d12fe0fb6 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.dto.ts @@ -0,0 +1,24 @@ +import { IsString, IsInt, IsBoolean, IsOptional } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; + +export class CreateSeatClassDto { + @ApiProperty({ example: 'Economy Seat' }) + @IsString() + name: string; + + @ApiPropertyOptional({ example: 'Standard economy seating' }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ example: 45000, description: 'Base price in minor currency units' }) + @IsInt() + basePrice: number; + + @ApiPropertyOptional({ example: true }) + @IsOptional() + @IsBoolean() + isActive?: boolean; +} + +export class UpdateSeatClassDto extends PartialType(CreateSeatClassDto) {} diff --git a/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.module.ts b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.module.ts new file mode 100644 index 000000000..a7e8648e1 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { SeatClassesController } from './seat-classes.controller'; +import { SeatClassesService } from './seat-classes.service'; + +@Module({ controllers: [SeatClassesController], providers: [SeatClassesService], exports: [SeatClassesService] }) +export class SeatClassesModule {} diff --git a/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.service.ts b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.service.ts new file mode 100644 index 000000000..b21eac0b6 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/seat-classes/seat-classes.service.ts @@ -0,0 +1,40 @@ +import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { CreateSeatClassDto, UpdateSeatClassDto } from './seat-classes.dto'; + +@Injectable() +export class SeatClassesService { + constructor(private prisma: PrismaService) {} + + private readonly coachInclude = { + coaches: { + select: { id: true, coachNumber: true, label: true, mode: true, totalUnits: true, _count: { select: { seats: true } } }, + orderBy: { label: 'asc' as const }, + }, + }; + + listSeatClasses() { + return this.prisma.seatClass.findMany({ orderBy: { createdAt: 'asc' }, include: this.coachInclude }); + } + + async getSeatClass(id: string) { + const sc = await this.prisma.seatClass.findUnique({ where: { id }, include: this.coachInclude }); + if (!sc) throw new NotFoundException('SeatClass not found'); + return sc; + } + + async createSeatClass(dto: CreateSeatClassDto) { + try { + return await this.prisma.seatClass.create({ data: dto, include: this.coachInclude }); + } catch (e: any) { + if (e.code === 'P2002') throw new ConflictException(`Seat class "${dto.name}" already exists`); + throw e; + } + } + + async updateSeatClass(id: string, dto: UpdateSeatClassDto) { + const sc = await this.prisma.seatClass.findUnique({ where: { id } }); + if (!sc) throw new NotFoundException('SeatClass not found'); + return this.prisma.seatClass.update({ where: { id }, data: dto, include: this.coachInclude }); + } +} diff --git a/apps/edr-passenger-api/src/modules/seats/seats.controller.ts b/apps/edr-passenger-api/src/modules/seats/seats.controller.ts index a872f337d..9059ba6f0 100644 --- a/apps/edr-passenger-api/src/modules/seats/seats.controller.ts +++ b/apps/edr-passenger-api/src/modules/seats/seats.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; import { SeatsService } from './seats.service'; import { HoldSeatsDto } from './seats.dto'; import { JwtGuard } from '../../common/jwt.guard'; @@ -8,10 +8,44 @@ import { JwtGuard } from '../../common/jwt.guard'; @Controller('seats') export class SeatsController { constructor(private service: SeatsService) {} - @Get('seatmap/:tripId') @ApiOperation({ summary: 'Get seat map for a trip' }) - getSeatMap(@Param('tripId') tripId: string, @Query('coachId') coachId?: string) { return this.service.getSeatMap(tripId, coachId); } - @Post('hold') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Hold seats for 15 minutes' }) + + // โ”€โ”€ Seat Map โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + @Get('seatmap/:scheduleId') + @ApiOperation({ summary: 'Get seat map for a schedule' }) + @ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' }) + @ApiQuery({ name: 'coachId', required: false, description: 'Filter by coach UUID' }) + @ApiResponse({ status: 200, description: 'Returns coaches with seats and seat class info' }) + getSeatMap(@Param('scheduleId') scheduleId: string, @Query('coachId') coachId?: string) { return this.service.getSeatMap(scheduleId, coachId); } + + // โ”€โ”€ Hold / Release โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + @Post('hold') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Hold seats for 15 minutes' }) + @ApiResponse({ status: 201, description: 'Seats held successfully' }) + @ApiResponse({ status: 409, description: 'One or more seats unavailable' }) holdSeats(@Body() dto: HoldSeatsDto) { return this.service.holdSeats(dto); } - @Delete('hold/:holdId') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Release a seat hold' }) + + @Delete('hold/:holdId') + @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') + @ApiOperation({ summary: 'Release a seat hold' }) + @ApiParam({ name: 'holdId', description: 'Hold UUID' }) + @ApiResponse({ status: 200, description: 'Hold released' }) + @ApiResponse({ status: 404, description: 'Hold not found' }) releaseHold(@Param('holdId') holdId: string) { return this.service.releaseHold(holdId); } + + @Get('export/csv/:scheduleId') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Export seats as CSV' }) + async exportCSV(@Param('scheduleId') scheduleId: string) { + const csv = await this.service.exportSeatsCSV(scheduleId); + return { csv, filename: `seats-${scheduleId}.csv` }; + } + + @Post('import/preview') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Preview CSV import' }) + previewCSV(@Body() body: { csv: string }) { + return this.service.previewSeatsCSV(body.csv); + } + + @Post('import/commit') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Commit CSV import' }) + importCSV(@Body() body: { scheduleId: string; csv: string; commit: boolean }) { + return this.service.importSeatsCSV(body.scheduleId, body.csv, body.commit); + } } diff --git a/apps/edr-passenger-api/src/modules/seats/seats.dto.ts b/apps/edr-passenger-api/src/modules/seats/seats.dto.ts index a36d76f18..6af6fe203 100644 --- a/apps/edr-passenger-api/src/modules/seats/seats.dto.ts +++ b/apps/edr-passenger-api/src/modules/seats/seats.dto.ts @@ -1,9 +1,9 @@ -import { IsString, IsArray } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsArray, IsOptional } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class HoldSeatsDto { - @ApiProperty() @IsString() tripId: string; - @ApiProperty() @IsString() passengerId: string; - @ApiProperty({ type: [String] }) @IsArray() seatIds: string[]; - @ApiProperty({ required: false }) fareQuoteId?: string; + @ApiProperty({ example: 'schedule-uuid' }) @IsString() scheduleId: string; + @ApiProperty({ example: 'passenger-uuid' }) @IsString() passengerId: string; + @ApiProperty({ type: [String], example: ['seat-uuid-1', 'seat-uuid-2'] }) @IsArray() seatIds: string[]; + @ApiPropertyOptional({ example: 'fare-quote-uuid' }) @IsOptional() @IsString() fareQuoteId?: string; } diff --git a/apps/edr-passenger-api/src/modules/seats/seats.service.spec.ts b/apps/edr-passenger-api/src/modules/seats/seats.service.spec.ts new file mode 100644 index 000000000..2a3b84539 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/seats/seats.service.spec.ts @@ -0,0 +1,82 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SeatsService } from './seats.service'; +import { PrismaService } from '../../common/prisma.service'; +import { ConflictException } from '@nestjs/common'; + +describe('SeatsService - Auto Assign', () => { + let service: SeatsService; + let prisma: PrismaService; + + const mockPrisma = { + seat: { + findMany: jest.fn(), + updateMany: jest.fn(), + }, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SeatsService, + { provide: PrismaService, useValue: mockPrisma }, + ], + }).compile(); + + service = module.get(SeatsService); + prisma = module.get(PrismaService); + jest.clearAllMocks(); + }); + + describe('autoAssignSeats', () => { + it('should assign contiguous seats in same row', async () => { + const mockSeats = [ + { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, + { id: 'seat-2', coachId: 'coach-1', row: 1, col: 'B' }, + { id: 'seat-3', coachId: 'coach-1', row: 1, col: 'C' }, + { id: 'seat-4', coachId: 'coach-1', row: 2, col: 'A' }, + ]; + + mockPrisma.seat.findMany.mockResolvedValue(mockSeats); + + const result = await service.autoAssignSeats('trip-1', 2, 'ECONOMY_REGULAR'); + + expect(result).toHaveLength(2); + expect(result).toEqual(['seat-1', 'seat-2']); + }); + + it('should throw error if not enough seats available', async () => { + mockPrisma.seat.findMany.mockResolvedValue([ + { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, + ]); + + await expect( + service.autoAssignSeats('trip-1', 3, 'ECONOMY_REGULAR'), + ).rejects.toThrow(ConflictException); + }); + + it('should respect eligibility filter', async () => { + const mockSeats = [ + { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A', eligibility: 'ACCESSIBLE' }, + { id: 'seat-2', coachId: 'coach-1', row: 1, col: 'B', eligibility: 'ACCESSIBLE' }, + ]; + + mockPrisma.seat.findMany.mockResolvedValue(mockSeats); + + const result = await service.autoAssignSeats('trip-1', 2, 'ECONOMY_REGULAR', 'ACCESSIBLE'); + + expect(result).toHaveLength(2); + }); + + it('should assign single seat', async () => { + const mockSeats = [ + { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, + ]; + + mockPrisma.seat.findMany.mockResolvedValue(mockSeats); + + const result = await service.autoAssignSeats('trip-1', 1, 'ECONOMY_REGULAR'); + + expect(result).toEqual(['seat-1']); + }); + }); +}); diff --git a/apps/edr-passenger-api/src/modules/seats/seats.service.ts b/apps/edr-passenger-api/src/modules/seats/seats.service.ts index 90234b6f2..606cb4902 100644 --- a/apps/edr-passenger-api/src/modules/seats/seats.service.ts +++ b/apps/edr-passenger-api/src/modules/seats/seats.service.ts @@ -7,18 +7,26 @@ import { Cron, CronExpression } from '@nestjs/schedule'; export class SeatsService { constructor(private prisma: PrismaService) {} - async getSeatMap(tripId: string, coachId?: string) { - const coaches = await this.prisma.coach.findMany({ where: { tripId, ...(coachId ? { id: coachId } : {}) }, include: { seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] } } }); + // โ”€โ”€ Seat Map โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + async getSeatMap(scheduleId: string, coachId?: string) { + const assignments = await this.prisma.coachAssignment.findMany({ + where: { scheduleId, ...(coachId ? { coachId } : {}) }, + include: { coach: { include: { seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] }, seatClass: true } } }, + orderBy: { positionNumber: 'asc' }, + }); return { - coaches: coaches.map((coach) => ({ - id: coach.id, - name: `Coach ${coach.label}`, - type: coach.serviceClass, - seats: coach.seats.map((s) => ({ id: s.id, number: s.label, status: s.status, kind: s.kind })), + coaches: assignments.map((a) => ({ + id: a.coach.id, + assignmentId: a.id, + name: `Coach ${a.coach.label}`, + seatClass: a.coach.seatClass.name, + positionNumber: a.positionNumber, + seats: a.coach.seats.map((s) => ({ id: s.id, number: s.label, status: s.status, kind: s.kind })), })), }; } + // โ”€โ”€ Hold / Release โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async holdSeats(dto: HoldSeatsDto) { const expiresAt = new Date(Date.now() + 15 * 60 * 1000); const hold = await this.prisma.$transaction(async (tx) => { @@ -26,9 +34,9 @@ export class SeatsService { const unavailable = seats.filter((s) => s.status === 'BOOKED' || s.status === 'BLOCKED' || (s.status === 'HELD' && s.heldUntil && s.heldUntil > new Date())); if (unavailable.length > 0) throw new ConflictException('One or more seats unavailable'); await tx.seat.updateMany({ where: { id: { in: dto.seatIds } }, data: { status: 'HELD', heldUntil: expiresAt } }); - return tx.seatHold.create({ data: { tripId: dto.tripId, passengerId: dto.passengerId, seatIds: dto.seatIds, fareQuoteId: dto.fareQuoteId, expiresAt } }); + return tx.seatHold.create({ data: { scheduleId: dto.scheduleId, passengerId: dto.passengerId, seatIds: dto.seatIds, fareQuoteId: dto.fareQuoteId, expiresAt } }); }); - return { id: hold.id, tripId: dto.tripId, seatIds: dto.seatIds, expiresAt }; + return { id: hold.id, scheduleId: dto.scheduleId, seatIds: dto.seatIds, expiresAt }; } async releaseHold(holdId: string) { @@ -42,6 +50,125 @@ export class SeatsService { async confirmSeats(seatIds: string[]) { await this.prisma.seat.updateMany({ where: { id: { in: seatIds } }, data: { status: 'BOOKED', heldUntil: null } }); } async releaseSeats(seatIds: string[]) { await this.prisma.seat.updateMany({ where: { id: { in: seatIds } }, data: { status: 'AVAILABLE', heldUntil: null } }); } + async autoAssignSeats(scheduleId: string, count: number, seatClassName: string, eligibility?: string): Promise { + const seats = await this.prisma.seat.findMany({ + where: { + coach: { seatClass: { name: seatClassName }, assignments: { some: { scheduleId } } }, + status: 'AVAILABLE', + ...(eligibility ? { eligibility } : {}), + }, + orderBy: [{ coach: { label: 'asc' } }, { row: 'asc' }, { col: 'asc' }], + }); + + if (seats.length < count) { + throw new ConflictException(`Only ${seats.length} seats available, requested ${count}`); + } + + const assigned = this.findContiguousSeats(seats, count); + return assigned.map((s) => s.id); + } + + private findContiguousSeats(seats: any[], count: number): any[] { + if (count === 1) return [seats[0]]; + + const grouped = new Map(); + for (const seat of seats) { + const key = `${seat.coachId}-${seat.row}`; + if (!grouped.has(key)) grouped.set(key, []); + grouped.get(key)!.push(seat); + } + + for (const rowSeats of grouped.values()) { + if (rowSeats.length >= count) { + return rowSeats.slice(0, count); + } + } + + return seats.slice(0, count); + } + + async exportSeatsCSV(scheduleId: string): Promise { + const assignments = await this.prisma.coachAssignment.findMany({ + where: { scheduleId }, + include: { coach: { include: { seats: { orderBy: [{ row: 'asc' }, { col: 'asc' }] } } } }, + }); + const rows = ['coachId,coachLabel,row,col,label,kind,status,premiumFeeMinor,eligibility']; + for (const a of assignments) { + for (const seat of a.coach.seats) { + rows.push(`${a.coach.id},${a.coach.label},${seat.row},${seat.col},${seat.label},${seat.kind},${seat.status},${seat.premiumFeeMinor},${seat.eligibility || ''}`); + } + } + return rows.join('\n'); + } + + async previewSeatsCSV(csvContent: string): Promise<{ valid: number; invalid: number; errors: string[] }> { + const lines = csvContent.trim().split('\n').slice(1); + const errors: string[] = []; + let valid = 0; + let invalid = 0; + + for (let i = 0; i < lines.length; i++) { + const parts = lines[i].split(','); + if (parts.length < 8) { + errors.push(`Line ${i + 2}: Invalid format`); + invalid++; + continue; + } + const [coachId, coachLabel, row, col, label, kind, status, premiumFeeMinor] = parts; + if (!coachId || !row || !col || !label) { + errors.push(`Line ${i + 2}: Missing required fields`); + invalid++; + continue; + } + valid++; + } + + return { valid, invalid, errors: errors.slice(0, 10) }; + } + + async importSeatsCSV(scheduleId: string, csvContent: string, commit: boolean): Promise<{ imported: number; errors: string[] }> { + const lines = csvContent.trim().split('\n').slice(1); + const errors: string[] = []; + let imported = 0; + + if (!commit) { + return { imported: 0, errors: ['Preview mode - use commit=true to apply changes'] }; + } + + for (let i = 0; i < lines.length; i++) { + try { + const parts = lines[i].split(','); + const [coachId, coachLabel, row, col, label, kind, status, premiumFeeMinor, eligibility] = parts; + + await this.prisma.seat.upsert({ + where: { coachId_row_col: { coachId, row: parseInt(row), col } }, + update: { + label, + kind: kind as any, + status: status as any, + premiumFeeMinor: parseInt(premiumFeeMinor) || 0, + eligibility: eligibility || null, + }, + create: { + coachId, + row: parseInt(row), + col, + label, + kind: kind as any, + status: status as any, + premiumFeeMinor: parseInt(premiumFeeMinor) || 0, + eligibility: eligibility || null, + }, + }); + imported++; + } catch (err) { + errors.push(`Line ${i + 2}: ${err instanceof Error ? err.message : String(err)}`); + } + } + + return { imported, errors: errors.slice(0, 10) }; + } + @Cron(CronExpression.EVERY_MINUTE) async expireHolds() { const expired = await this.prisma.seatHold.findMany({ where: { expiresAt: { lt: new Date() } } }); diff --git a/apps/edr-passenger-api/src/modules/segments/booking-flow-example.ts b/apps/edr-passenger-api/src/modules/segments/booking-flow-example.ts new file mode 100644 index 000000000..8b46a77d1 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/booking-flow-example.ts @@ -0,0 +1,238 @@ +/** + * SEGMENT-BASED SEAT RESERVATION EXAMPLE + * + * Demonstrates the complete flow for booking Addis Ababa โ†’ Dire Dawa + * on the Addis Ababa โ†’ Djibouti route with segment-based seat management. + * + * Route: Addis Ababa (seq:1) โ†’ Adama (seq:2) โ†’ Awash (seq:3) โ†’ Dire Dawa (seq:4) โ†’ Aysha (seq:5) โ†’ Djibouti (seq:6) + * Booking: Addis Ababa โ†’ Dire Dawa (segments: 1โ†’2, 2โ†’3, 3โ†’4) + */ + +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +async function exampleBookingFlow() { + console.log('=== SEGMENT-BASED BOOKING FLOW ===\n'); + + const scheduleId = 'schedule_add_dji_001'; + const passengerId = 'passenger_kelemu'; + const seatIds = ['seat_coach_a_1a', 'seat_coach_a_1b']; + const originStationId = 'st_ADD'; + const destinationStationId = 'st_DRE'; + + try { + console.log('1. Checking seat availability...'); + const segments = await getJourneySegments(scheduleId, originStationId, destinationStationId); + console.log('Journey segments:', segments.map(s => `${s.fromName} โ†’ ${s.toName}`)); + + console.log('\n2. Holding seats...'); + const holdResult = await holdSeatsTransaction(scheduleId, seatIds, passengerId, originStationId, destinationStationId); + console.log('Hold created:', holdResult); + + console.log('\n3. Processing payment...'); + await new Promise(resolve => setTimeout(resolve, 5000)); + + console.log('\n4. Confirming booking...'); + const bookingId = 'booking_' + Date.now(); + const confirmResult = await confirmBookingTransaction(holdResult.holdId, bookingId, segments); + console.log('Booking confirmed:', confirmResult); + + console.log('\n5. Simulating trip progress...'); + await simulateTripProgress(scheduleId, segments); + + } catch (error) { + console.error('Booking flow error:', error); + } +} + +async function getJourneySegments(scheduleId: string, originStationId: string, destinationStationId: string) { + const stopTimes = await prisma.tripStopTime.findMany({ + where: { scheduleId }, + include: { station: true }, + orderBy: { sequence: 'asc' }, + }); + + const originStop = stopTimes.find(st => st.stationId === originStationId); + const destinationStop = stopTimes.find(st => st.stationId === destinationStationId); + + if (!originStop || !destinationStop || originStop.sequence >= destinationStop.sequence) { + throw new Error('Invalid origin/destination'); + } + + const segments = []; + for (let i = originStop.sequence; i < destinationStop.sequence; i++) { + const fromStop = stopTimes.find(st => st.sequence === i); + const toStop = stopTimes.find(st => st.sequence === i + 1); + if (fromStop && toStop) { + segments.push({ + fromStationId: fromStop.stationId, + toStationId: toStop.stationId, + fromSequence: fromStop.sequence, + toSequence: toStop.sequence, + fromName: fromStop.station.name, + toName: toStop.station.name, + }); + } + } + return segments; +} + +async function holdSeatsTransaction(scheduleId: string, seatIds: string[], passengerId: string, originStationId: string, destinationStationId: string) { + return prisma.$transaction(async (tx) => { + console.log(' โ†’ Starting seat hold transaction...'); + + const seats = await tx.seat.findMany({ where: { id: { in: seatIds } }, include: { coach: true } }); + if (seats.length !== seatIds.length) throw new Error('Some seats not found'); + + for (const seat of seats) { + if (seat.status !== 'AVAILABLE') { + throw new Error(`Seat ${seat.label} is not available (status: ${seat.status})`); + } + } + + const expiresAt = new Date(Date.now() + 10 * 60 * 1000); + const seatHold = await tx.seatHold.create({ + data: { scheduleId, seatIds, passengerId, expiresAt }, + }); + + await tx.seat.updateMany({ where: { id: { in: seatIds } }, data: { status: 'HELD', heldUntil: expiresAt } }); + + console.log(' โ†’ Seats held successfully'); + return { holdId: seatHold.id, expiresAt, seats: seatIds.length }; + }); +} + +async function confirmBookingTransaction(holdId: string, bookingId: string, segments: any[]) { + return prisma.$transaction(async (tx) => { + console.log(' โ†’ Starting booking confirmation transaction...'); + + const hold = await tx.seatHold.findUnique({ where: { id: holdId } }); + if (!hold || hold.expiresAt < new Date()) throw new Error('Hold expired or not found'); + + const booking = await tx.booking.create({ + data: { + id: bookingId, + bookingRef: 'BK' + Date.now().toString().slice(-6), + passengerId: hold.passengerId, + scheduleId: hold.scheduleId, + status: 'CONFIRMED', + totalMinor: 45000, + currency: 'ETB', + }, + }); + + const journey = await tx.journey.create({ + data: { passengerId: hold.passengerId, status: 'CONFIRMED', totalMinor: 45000, currency: 'ETB' }, + }); + + for (const seatId of hold.seatIds) { + for (let i = 0; i < segments.length; i++) { + await tx.journeySegment.create({ + data: { + journeyId: journey.id, + scheduleId: hold.scheduleId, + segmentOrder: i + 1, + seatId, + departureStationId: segments[i].fromStationId, + arrivalStationId: segments[i].toStationId, + }, + }); + } + } + + for (const seatId of hold.seatIds) { + await tx.bookingSeat.create({ data: { bookingId, seatId, passengerName: 'Kelemu Ketsela' } }); + } + + await tx.seat.updateMany({ where: { id: { in: hold.seatIds } }, data: { status: 'BOOKED', heldUntil: null } }); + await tx.seatHold.delete({ where: { id: holdId } }); + + console.log(' โ†’ Booking confirmed successfully'); + return { bookingId, bookingRef: booking.bookingRef, confirmedSeats: hold.seatIds.length, segments: segments.length }; + }); +} + +async function simulateTripProgress(scheduleId: string, bookedSegments: any[]) { + console.log(' โ†’ Simulating trip progress...'); + + for (const segment of bookedSegments) { + console.log(` โ†’ Train approaching ${segment.toName}...`); + + await prisma.tripLiveStatus.upsert({ + where: { scheduleId }, + update: { currentLocationLabel: segment.toName, progressPercent: Math.round((segment.toSequence / 4) * 100) }, + create: { + scheduleId, + state: 'EN_ROUTE', + currentLocationLabel: segment.toName, + progressPercent: Math.round((segment.toSequence / 4) * 100), + delayMinutes: 0, + }, + }); + + if (segment.toName === 'Dire Dawa') { + console.log(' โ†’ Passengers reached destination, releasing seats...'); + await releaseSeatsAtStation(scheduleId, segment.toStationId); + } + + await new Promise(resolve => setTimeout(resolve, 2000)); + } +} + +async function releaseSeatsAtStation(scheduleId: string, stationId: string) { + return prisma.$transaction(async (tx) => { + const completedSegments = await tx.journeySegment.findMany({ + where: { scheduleId, arrivalStationId: stationId }, + include: { journey: { include: { journeySegments: { where: { scheduleId } } } } }, + }); + + const seatsToRelease: string[] = []; + + for (const segment of completedSegments) { + const passengerSegments = segment.journey.journeySegments.filter((js: any) => js.seatId === segment.seatId); + const maxOrder = Math.max(...passengerSegments.map((js: any) => js.segmentOrder)); + if (segment.segmentOrder === maxOrder) seatsToRelease.push(segment.seatId!); + } + + if (seatsToRelease.length > 0) { + await tx.seat.updateMany({ where: { id: { in: seatsToRelease } }, data: { status: 'AVAILABLE' } }); + console.log(` โ†’ Released ${seatsToRelease.length} seats at station`); + } + + return seatsToRelease; + }); +} + +async function checkOverlappingReservations(tx: any, scheduleId: string, seatId: string, segments: any[]) { + const activeHolds = await tx.seatHold.findMany({ + where: { scheduleId, seatIds: { has: seatId }, expiresAt: { gt: new Date() } }, + }); + + const activeBookings = await tx.journeySegment.findMany({ + where: { + scheduleId, + seatId, + journey: { status: { in: ['PENDING_PAYMENT', 'CONFIRMED'] } }, + }, + }); + + return [...activeHolds, ...activeBookings]; +} + +if (require.main === module) { + exampleBookingFlow() + .then(() => console.log('\n=== EXAMPLES COMPLETED ===')) + .catch(console.error) + .finally(() => prisma.$disconnect()); +} + +export { + exampleBookingFlow, + getJourneySegments, + holdSeatsTransaction, + confirmBookingTransaction, + simulateTripProgress, + releaseSeatsAtStation, + checkOverlappingReservations, +}; diff --git a/apps/edr-passenger-api/src/modules/segments/enhanced-seats.service.ts b/apps/edr-passenger-api/src/modules/segments/enhanced-seats.service.ts new file mode 100644 index 000000000..275ab077f --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/enhanced-seats.service.ts @@ -0,0 +1,198 @@ +import { Injectable, BadRequestException, ConflictException } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { SegmentsService, Segment } from '../segments/segments.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +export interface SeatHoldRequest { + scheduleId: string; + seatIds: string[]; + passengerId: string; + originStationId: string; + destinationStationId: string; + fareQuoteId?: string; +} + +export interface BookingConfirmRequest { + holdId: string; + bookingId: string; +} + +@Injectable() +export class EnhancedSeatsService { + constructor( + private prisma: PrismaService, + private segmentsService: SegmentsService, + private eventEmitter: EventEmitter2, + ) {} + + async holdSeats(request: SeatHoldRequest) { + return this.prisma.$transaction(async (tx) => { + const segments = await this.segmentsService.getJourneySegments(request.scheduleId, request.originStationId, request.destinationStationId); + + for (const seatId of request.seatIds) { + const seat = await tx.seat.findUnique({ where: { id: seatId } }); + if (!seat) throw new BadRequestException(`Seat ${seatId} not found`); + if (seat.status === 'BLOCKED') throw new BadRequestException(`Seat ${seat.label} is blocked`); + const overlaps = await this.segmentsService.getOverlappingReservations(request.scheduleId, seatId, segments); + if (overlaps.length > 0) throw new ConflictException(`Seat ${seat.label} is not available for the requested segments`); + } + + const expiresAt = new Date(Date.now() + 10 * 60 * 1000); + // Encode origin/destination into fareQuoteId so confirmBooking can resolve the leg range + // Format: "leg:{originStationId}:{destinationStationId}" (or preserve actual fareQuoteId) + const legKey = request.fareQuoteId ?? `leg:${request.originStationId}:${request.destinationStationId}`; + const seatHold = await tx.seatHold.create({ + data: { scheduleId: request.scheduleId, seatIds: request.seatIds, passengerId: request.passengerId, fareQuoteId: legKey, expiresAt }, + }); + + await tx.seat.updateMany({ where: { id: { in: request.seatIds } }, data: { status: 'HELD', heldUntil: expiresAt } }); + + this.eventEmitter.emit('seats.held', { holdId: seatHold.id, scheduleId: request.scheduleId, seatIds: request.seatIds, segments }); + + return { holdId: seatHold.id, expiresAt, segments, seats: request.seatIds }; + }); + } + + async confirmBooking(request: BookingConfirmRequest) { + return this.prisma.$transaction(async (tx) => { + const hold = await tx.seatHold.findUnique({ where: { id: request.holdId } }); + if (!hold) throw new BadRequestException('Seat hold not found'); + if (hold.expiresAt < new Date()) throw new BadRequestException('Seat hold has expired'); + + const booking = await tx.booking.findUnique({ where: { id: request.bookingId } }); + if (!booking) throw new BadRequestException('Booking not found'); + + const schedule = await tx.trainSchedule.findUnique({ + where: { id: hold.scheduleId }, + include: { stopTimes: { orderBy: { sequence: 'asc' } } }, + }); + if (!schedule) throw new BadRequestException('Schedule not found'); + + // Resolve the passenger's leg range from the hold's fareQuoteId (encoded as "leg:originId:destId") + const legKey = hold.fareQuoteId ?? ''; + let originStationId: string | undefined; + let destinationStationId: string | undefined; + if (legKey.startsWith('leg:')) { + const parts = legKey.split(':'); + originStationId = parts[1]; + destinationStationId = parts[2]; + } else { + // Fall back to booking's own origin/destination if available + originStationId = (booking as any).originStationId; + destinationStationId = (booking as any).destinationStationId; + } + + const originStop = originStationId ? schedule.stopTimes.find(s => s.stationId === originStationId) : undefined; + const destStop = destinationStationId ? schedule.stopTimes.find(s => s.stationId === destinationStationId) : undefined; + const fromSeq = originStop?.sequence ?? schedule.stopTimes[0].sequence; + const toSeq = destStop?.sequence ?? schedule.stopTimes[schedule.stopTimes.length - 1].sequence; + + const segments: Segment[] = []; + for (let i = fromSeq; i < toSeq; i++) { + const fromStop = schedule.stopTimes.find(s => s.sequence === i); + const toStop = schedule.stopTimes.find(s => s.sequence === i + 1); + if (fromStop && toStop) { + segments.push({ + fromStationId: fromStop.stationId, + toStationId: toStop.stationId, + fromSequence: fromStop.sequence, + toSequence: toStop.sequence, + fromName: '', + toName: '', + }); + } + } + + const journey = await tx.journey.create({ + data: { passengerId: hold.passengerId, status: 'CONFIRMED', totalMinor: booking.totalMinor, currency: booking.currency }, + }); + + for (const seatId of hold.seatIds) { + for (let i = 0; i < segments.length; i++) { + await tx.journeySegment.create({ + data: { + journeyId: journey.id, + scheduleId: hold.scheduleId, + segmentOrder: i + 1, + seatId, + departureStationId: segments[i].fromStationId, + arrivalStationId: segments[i].toStationId, + }, + }); + } + } + + await tx.seat.updateMany({ where: { id: { in: hold.seatIds } }, data: { status: 'BOOKED', heldUntil: null } }); + await tx.seatHold.delete({ where: { id: request.holdId } }); + + this.eventEmitter.emit('booking.confirmed', { bookingId: request.bookingId, scheduleId: hold.scheduleId, seatIds: hold.seatIds, segments }); + + return { bookingId: request.bookingId, confirmedSeats: hold.seatIds, segments }; + }); + } + + async releaseSeats(scheduleId: string, currentStationId: string) { + return this.prisma.$transaction(async (tx) => { + const completedSegments = await tx.journeySegment.findMany({ + where: { scheduleId, arrivalStationId: currentStationId }, + include: { journey: { include: { journeySegments: { where: { scheduleId } } } } }, + }); + + const seatsToRelease: string[] = []; + for (const segment of completedSegments) { + const allSegments = segment.journey.journeySegments.filter((js: any) => js.seatId === segment.seatId); + const maxSegmentOrder = Math.max(...allSegments.map((js: any) => js.segmentOrder)); + if (segment.segmentOrder === maxSegmentOrder) seatsToRelease.push(segment.seatId!); + } + + if (seatsToRelease.length > 0) { + await tx.seat.updateMany({ where: { id: { in: seatsToRelease } }, data: { status: 'AVAILABLE' } }); + this.eventEmitter.emit('seats.released', { scheduleId, stationId: currentStationId, releasedSeats: seatsToRelease }); + } + + return { releasedSeats: seatsToRelease, stationId: currentStationId }; + }); + } + + async expireHolds() { + return this.prisma.$transaction(async (tx) => { + const expiredHolds = await tx.seatHold.findMany({ where: { expiresAt: { lt: new Date() } } }); + const expiredSeatIds = expiredHolds.flatMap(h => h.seatIds); + + if (expiredSeatIds.length > 0) { + await tx.seat.updateMany({ where: { id: { in: expiredSeatIds } }, data: { status: 'AVAILABLE', heldUntil: null } }); + await tx.seatHold.deleteMany({ where: { expiresAt: { lt: new Date() } } }); + this.eventEmitter.emit('holds.expired', { expiredHolds: expiredHolds.length, releasedSeats: expiredSeatIds }); + } + + return { expiredHolds: expiredHolds.length, releasedSeats: expiredSeatIds }; + }); + } + + async getSeatAvailability(scheduleId: string, originStationId: string, destinationStationId: string) { + const segments = await this.segmentsService.getJourneySegments(scheduleId, originStationId, destinationStationId); + + const schedule = await this.prisma.trainSchedule.findUnique({ + where: { id: scheduleId }, + include: { coachAssignments: { include: { coach: { include: { seats: true, seatClass: true } } } } }, + }); + if (!schedule) throw new BadRequestException('Schedule not found'); + + const availableSeats = []; + for (const assignment of schedule.coachAssignments) { + for (const seat of assignment.coach.seats) { + const overlaps = await this.segmentsService.getOverlappingReservations(scheduleId, seat.id, segments); + if (overlaps.length === 0 && seat.status === 'AVAILABLE') { + availableSeats.push({ + id: seat.id, label: seat.label, + coach: assignment.coach.label, + seatClass: assignment.coach.seatClass.name, + row: seat.row, col: seat.col, + }); + } + } + } + + return { segments, availableSeats, totalAvailable: availableSeats.length }; + } +} diff --git a/apps/edr-passenger-api/src/modules/segments/segments.controller.ts b/apps/edr-passenger-api/src/modules/segments/segments.controller.ts new file mode 100644 index 000000000..5a45e38c5 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/segments.controller.ts @@ -0,0 +1,132 @@ +import { Controller, Post, Get, Body, Query, Param } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { EnhancedSeatsService } from './enhanced-seats.service'; +import { HoldSeatsDto, ConfirmBookingDto, SeatAvailabilityDto, ReleaseSeatsDto } from './segments.dto'; + +@ApiTags('Segment-based Seats') +@Controller('segments/seats') +export class SegmentSeatsController { + constructor(private enhancedSeatsService: EnhancedSeatsService) {} + + @Post('hold') + @ApiOperation({ + summary: 'Hold seats for specific journey segments', + description: 'Reserve seats for a partial journey (e.g., Addis Ababa โ†’ Dire Dawa) with 10-minute expiry' + }) + @ApiResponse({ + status: 201, + description: 'Seats held successfully', + schema: { + example: { + holdId: 'hold_123', + expiresAt: '2024-01-15T10:10:00Z', + segments: [ + { fromName: 'Addis Ababa', toName: 'Adama', fromSequence: 0, toSequence: 1 }, + { fromName: 'Adama', toName: 'Awash', fromSequence: 1, toSequence: 2 }, + { fromName: 'Awash', toName: 'Dire Dawa', fromSequence: 2, toSequence: 3 } + ], + seats: ['seat_1', 'seat_2'] + } + } + }) + @ApiResponse({ status: 409, description: 'Seats not available for requested segments' }) + async holdSeats(@Body() dto: HoldSeatsDto) { + return this.enhancedSeatsService.holdSeats({ + scheduleId: dto.scheduleId, + seatIds: dto.seatIds, + passengerId: dto.passengerId, + originStationId: dto.originStationId, + destinationStationId: dto.destinationStationId, + fareQuoteId: dto.fareQuoteId, + }); + } + + @Post('confirm') + @ApiOperation({ + summary: 'Confirm booking and convert hold to reservation', + description: 'Convert seat hold to confirmed booking after payment success' + }) + @ApiResponse({ + status: 200, + description: 'Booking confirmed successfully', + schema: { + example: { + bookingId: 'booking_123', + confirmedSeats: ['seat_1', 'seat_2'], + segments: [ + { fromName: 'Addis Ababa', toName: 'Adama' }, + { fromName: 'Adama', toName: 'Awash' }, + { fromName: 'Awash', toName: 'Dire Dawa' } + ] + } + } + }) + @ApiResponse({ status: 400, description: 'Hold expired or not found' }) + async confirmBooking(@Body() dto: ConfirmBookingDto) { + return this.enhancedSeatsService.confirmBooking(dto); + } + + @Post('release') + @ApiOperation({ + summary: 'Release seats when train reaches station', + description: 'Automatically release seats for passengers who have reached their destination' + }) + @ApiResponse({ + status: 200, + description: 'Seats released successfully', + schema: { + example: { + releasedSeats: ['seat_1', 'seat_2'], + stationId: 'st_DRE' + } + } + }) + async releaseSeats(@Body() dto: ReleaseSeatsDto) { + return this.enhancedSeatsService.releaseSeats(dto.scheduleId, dto.currentStationId); + } + + @Get('availability') + @ApiOperation({ + summary: 'Check seat availability for journey segments', + description: 'Get available seats for a specific origin-destination pair' + }) + @ApiResponse({ + status: 200, + description: 'Seat availability retrieved', + schema: { + example: { + segments: [ + { fromName: 'Addis Ababa', toName: 'Adama', fromSequence: 0, toSequence: 1 }, + { fromName: 'Adama', toName: 'Awash', fromSequence: 1, toSequence: 2 } + ], + availableSeats: [ + { id: 'seat_1', label: '1A', coach: 'A', seatClass: 'Economy Regular', row: 1, col: 'A' }, + { id: 'seat_2', label: '1B', coach: 'A', seatClass: 'Economy Regular', row: 1, col: 'B' } + ], + totalAvailable: 2 + } + } + }) + async getSeatAvailability(@Query() dto: SeatAvailabilityDto) { + return this.enhancedSeatsService.getSeatAvailability(dto.scheduleId, dto.originStationId, dto.destinationStationId); + } + + @Post('expire-holds') + @ApiOperation({ + summary: 'Expire old seat holds (background job)', + description: 'Release seats from expired holds and make them available' + }) + @ApiResponse({ + status: 200, + description: 'Expired holds processed', + schema: { + example: { + expiredHolds: 5, + releasedSeats: ['seat_1', 'seat_2', 'seat_3'] + } + } + }) + async expireHolds() { + return this.enhancedSeatsService.expireHolds(); + } +} \ No newline at end of file diff --git a/apps/edr-passenger-api/src/modules/segments/segments.dto.ts b/apps/edr-passenger-api/src/modules/segments/segments.dto.ts new file mode 100644 index 000000000..3cc335c11 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/segments.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsArray, IsOptional } from 'class-validator'; + +export class HoldSeatsDto { + @ApiProperty({ example: 'schedule-uuid' }) @IsString() scheduleId: string; + @ApiProperty({ example: ['seat_1', 'seat_2'] }) @IsArray() @IsString({ each: true }) seatIds: string[]; + @ApiProperty({ example: 'passenger-uuid' }) @IsString() passengerId: string; + @ApiProperty({ example: 'st_ADD' }) @IsString() originStationId: string; + @ApiProperty({ example: 'st_DJI' }) @IsString() destinationStationId: string; + @ApiPropertyOptional({ example: 'quote-uuid' }) @IsOptional() @IsString() fareQuoteId?: string; +} + +export class ConfirmBookingDto { + @ApiProperty({ example: 'hold-uuid' }) @IsString() holdId: string; + @ApiProperty({ example: 'booking-uuid' }) @IsString() bookingId: string; +} + +export class SeatAvailabilityDto { + @ApiProperty({ example: 'schedule-uuid' }) @IsString() scheduleId: string; + @ApiProperty({ example: 'st_ADD' }) @IsString() originStationId: string; + @ApiProperty({ example: 'st_DJI' }) @IsString() destinationStationId: string; +} + +export class ReleaseSeatsDto { + @ApiProperty({ example: 'schedule-uuid' }) @IsString() scheduleId: string; + @ApiProperty({ example: 'st_DJI' }) @IsString() currentStationId: string; +} diff --git a/apps/edr-passenger-api/src/modules/segments/segments.module.ts b/apps/edr-passenger-api/src/modules/segments/segments.module.ts new file mode 100644 index 000000000..f82da7fa9 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/segments.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { SegmentsService } from './segments.service'; +import { EnhancedSeatsService } from './enhanced-seats.service'; +import { TripProgressService } from './trip-progress.service'; +import { SegmentSeatsController } from './segments.controller'; +import { PrismaService } from '../../common/prisma.service'; + +@Module({ + controllers: [SegmentSeatsController], + providers: [ + SegmentsService, + EnhancedSeatsService, + TripProgressService, + PrismaService + ], + exports: [ + SegmentsService, + EnhancedSeatsService, + TripProgressService + ] +}) +export class SegmentsModule {} \ No newline at end of file diff --git a/apps/edr-passenger-api/src/modules/segments/segments.service.ts b/apps/edr-passenger-api/src/modules/segments/segments.service.ts new file mode 100644 index 000000000..8fe525bcb --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/segments.service.ts @@ -0,0 +1,130 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; + +export interface Segment { + fromStationId: string; + toStationId: string; + fromSequence: number; + toSequence: number; + fromName: string; + toName: string; +} + +@Injectable() +export class SegmentsService { + constructor(private prisma: PrismaService) {} + + async getJourneySegments( + scheduleId: string, + originStationId: string, + destinationStationId: string, + ): Promise { + const stopTimes = await this.prisma.tripStopTime.findMany({ + where: { scheduleId }, + include: { station: true }, + orderBy: { sequence: 'asc' }, + }); + + const originStop = stopTimes.find(st => st.stationId === originStationId); + const destStop = stopTimes.find(st => st.stationId === destinationStationId); + + if (!originStop || !destStop) { + throw new BadRequestException('Origin or destination station not found on this schedule'); + } + if (originStop.sequence >= destStop.sequence) { + throw new BadRequestException('Origin must come before destination'); + } + + const segments: Segment[] = []; + for (let i = originStop.sequence; i < destStop.sequence; i++) { + const fromStop = stopTimes.find(st => st.sequence === i); + const toStop = stopTimes.find(st => st.sequence === i + 1); + if (fromStop && toStop) { + segments.push({ + fromStationId: fromStop.stationId, + toStationId: toStop.stationId, + fromSequence: fromStop.sequence, + toSequence: toStop.sequence, + fromName: fromStop.station.name, + toName: toStop.station.name, + }); + } + } + return segments; + } + + /** True if two segment ranges overlap: [a.from, a.to) โˆฉ [b.from, b.to) โ‰  โˆ… */ + segmentsOverlap(segments1: Segment[], segments2: Segment[]): boolean { + for (const s1 of segments1) { + for (const s2 of segments2) { + if (s1.fromSequence < s2.toSequence && s2.fromSequence < s1.toSequence) return true; + } + } + return false; + } + + /** + * Returns conflicts for a seat on a schedule for the requested segment range. + * Checks: + * 1. Active SeatHolds โ€” resolved to sequence range via JourneySegment if available, + * otherwise treated as full-schedule block. + * 2. Active BookingSeats โ€” resolved via JourneySegment sequence ranges. + */ + async getOverlappingReservations( + scheduleId: string, + seatId: string, + requestedSegments: Segment[], + ) { + const overlaps: { type: string; id: string }[] = []; + const reqFrom = Math.min(...requestedSegments.map(s => s.fromSequence)); + const reqTo = Math.max(...requestedSegments.map(s => s.toSequence)); + + // โ”€โ”€ 1. Active holds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + const activeHolds = await this.prisma.seatHold.findMany({ + where: { scheduleId, seatIds: { has: seatId }, expiresAt: { gt: new Date() } }, + }); + + for (const hold of activeHolds) { + // Resolve hold range from JourneySegments created at hold time + const holdSegs = await this.prisma.journeySegment.findMany({ + where: { scheduleId, seatId }, + include: { schedule: { include: { stopTimes: true } } }, + }); + + if (holdSegs.length === 0) { + // No journey segments yet โ€” conservative: treat as full-schedule conflict + overlaps.push({ type: 'hold', id: hold.id }); + continue; + } + + for (const js of holdSegs) { + const depSeq = js.schedule.stopTimes.find(s => s.stationId === js.departureStationId)?.sequence; + const arrSeq = js.schedule.stopTimes.find(s => s.stationId === js.arrivalStationId)?.sequence; + if (depSeq !== undefined && arrSeq !== undefined && depSeq < reqTo && reqFrom < arrSeq) { + overlaps.push({ type: 'hold', id: hold.id }); + break; + } + } + } + + // โ”€โ”€ 2. Active bookings via JourneySegment โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + const bookedSegments = await this.prisma.journeySegment.findMany({ + where: { + scheduleId, + seatId, + journey: { status: { in: ['CONFIRMED', 'PENDING_PAYMENT'] } }, + }, + include: { schedule: { include: { stopTimes: true } } }, + }); + + for (const js of bookedSegments) { + const depSeq = js.schedule.stopTimes.find(s => s.stationId === js.departureStationId)?.sequence; + const arrSeq = js.schedule.stopTimes.find(s => s.stationId === js.arrivalStationId)?.sequence; + if (depSeq !== undefined && arrSeq !== undefined && depSeq < reqTo && reqFrom < arrSeq) { + overlaps.push({ type: 'booking', id: js.journeyId }); + } + } + + return overlaps; + } +} diff --git a/apps/edr-passenger-api/src/modules/segments/trip-progress.service.ts b/apps/edr-passenger-api/src/modules/segments/trip-progress.service.ts new file mode 100644 index 000000000..f67808385 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/segments/trip-progress.service.ts @@ -0,0 +1,226 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../common/prisma.service'; +import { EnhancedSeatsService } from './enhanced-seats.service'; +import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +@Injectable() +export class TripProgressService { + constructor( + private prisma: PrismaService, + private enhancedSeatsService: EnhancedSeatsService, + private eventEmitter: EventEmitter2 + ) {} + + /** + * Update trip progress and trigger seat releases + */ + async updateTripProgress(tripId: string, currentStationId: string, progressPercent: number) { + return this.prisma.$transaction(async (tx) => { + // 1. Update trip live status + await tx.tripLiveStatus.upsert({ + where: { scheduleId: tripId }, + update: { + currentLocationLabel: currentStationId, + progressPercent, + updatedAt: new Date() + }, + create: { + scheduleId: tripId, + state: 'EN_ROUTE', + currentLocationLabel: currentStationId, + progressPercent, + delayMinutes: 0, + updatedAt: new Date() + } + }); + + // 2. Get station name for comparison + const station = await tx.station.findUnique({ + where: { id: currentStationId } + }); + + if (station) { + // 3. Trigger seat release for passengers reaching destination + const releaseResult = await this.enhancedSeatsService.releaseSeats(tripId, currentStationId); + + // 4. Emit progress update event + this.eventEmitter.emit('trip.progress.updated', { + tripId, + currentStation: station.name, + progressPercent, + releasedSeats: releaseResult.releasedSeats + }); + + return { + tripId, + currentStation: station.name, + progressPercent, + releasedSeats: releaseResult.releasedSeats.length, + updatedAt: new Date() + }; + } + + return { tripId, currentStation: currentStationId, progressPercent, releasedSeats: 0 }; + }); + } + + /** + * Simulate trip progress (for testing/demo) + */ + async simulateTripProgress(tripId: string) { + const trip = await this.prisma.trainSchedule.findUnique({ + where: { id: tripId }, + include: { + stopTimes: { + include: { station: true }, + orderBy: { sequence: 'asc' } + } + } + }); + + if (!trip) { + throw new Error('Trip not found'); + } + + // Simulate progress through each station + for (let i = 0; i < trip.stopTimes.length; i++) { + const stopTime = trip.stopTimes[i]; + const progressPercent = Math.round((i / (trip.stopTimes.length - 1)) * 100); + + await this.updateTripProgress(tripId, stopTime.stationId, progressPercent); + + // Emit station arrival event + this.eventEmitter.emit('trip.station.arrived', { + tripId, + stationId: stopTime.stationId, + stationName: stopTime.station.name, + sequence: stopTime.sequence, + progressPercent + }); + + // Wait 30 seconds between stations (for demo) + await new Promise(resolve => setTimeout(resolve, 30000)); + } + } + + /** + * Handle trip completion + */ + @OnEvent('trip.completed') + async handleTripCompleted(payload: { tripId: string }) { + // Release all remaining seats for this trip + const trip = await this.prisma.trainSchedule.findUnique({ + where: { id: payload.tripId }, + include: { + coachAssignments: { + include: { + coach: { + include: { + seats: { + where: { status: 'BOOKED' } + } + } + } + } + } + } + }); + + if (trip) { + const bookedSeatIds = trip.coachAssignments.flatMap(assignment => + assignment.coach.seats.map(seat => seat.id) + ); + + if (bookedSeatIds.length > 0) { + await this.prisma.seat.updateMany({ + where: { id: { in: bookedSeatIds } }, + data: { status: 'AVAILABLE' } + }); + + this.eventEmitter.emit('trip.seats.released', { + tripId: payload.tripId, + releasedSeats: bookedSeatIds + }); + } + } + } + + /** + * Background job to expire holds every minute + */ + @Cron(CronExpression.EVERY_MINUTE) + async expireHoldsJob() { + try { + const result = await this.enhancedSeatsService.expireHolds(); + if (result.expiredHolds > 0) { + console.log(`Expired ${result.expiredHolds} holds, released ${result.releasedSeats.length} seats`); + } + } catch (error) { + console.error('Error expiring holds:', error); + } + } + + /** + * Get current trip status with seat availability + */ + async getTripStatus(tripId: string) { + const trip = await this.prisma.trainSchedule.findUnique({ + where: { id: tripId }, + include: { + liveStatus: true, + stopTimes: { + include: { station: true }, + orderBy: { sequence: 'asc' } + }, + coachAssignments: { + include: { + coach: { + include: { seats: true } + } + } + } + } + }); + + if (!trip) { + throw new Error('Trip not found'); + } + + const seatSummary = { + total: 0, + available: 0, + held: 0, + booked: 0, + blocked: 0 + }; + + trip.coachAssignments.forEach(assignment => { + assignment.coach.seats.forEach(seat => { + seatSummary.total++; + const status = seat.status.toLowerCase() as keyof typeof seatSummary; + if (status in seatSummary) { + seatSummary[status]++; + } + }); + }); + + return { + tripId, + status: trip.status, + currentLocation: trip.liveStatus?.currentLocationLabel, + progressPercent: trip.liveStatus?.progressPercent || 0, + delayMinutes: trip.liveStatus?.delayMinutes || 0, + stations: trip.stopTimes.map(st => ({ + id: st.stationId, + name: st.station.name, + sequence: st.sequence, + plannedArrival: st.plannedArrivalAt, + plannedDeparture: st.plannedDepartureAt, + actualArrival: st.actualArrivalAt + })), + seatSummary, + lastUpdated: trip.liveStatus?.updatedAt + }; + } +} \ No newline at end of file diff --git a/apps/edr-passenger-api/src/modules/tickets/tickets.controller.ts b/apps/edr-passenger-api/src/modules/tickets/tickets.controller.ts index 407af7234..7b871db3f 100644 --- a/apps/edr-passenger-api/src/modules/tickets/tickets.controller.ts +++ b/apps/edr-passenger-api/src/modules/tickets/tickets.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { TicketsService } from './tickets.service'; import { JwtGuard } from '../../common/jwt.guard'; @@ -9,6 +9,38 @@ import { JwtGuard } from '../../common/jwt.guard'; @ApiBearerAuth('JWT-auth') export class TicketsController { constructor(private service: TicketsService) {} - @Get(':bookingRef') @ApiOperation({ summary: 'Get ticket by booking reference' }) getByRef(@Param('bookingRef') ref: string) { return this.service.getByRef(ref); } - @Post(':bookingRef/validate') @ApiOperation({ summary: 'Validate ticket at gate (staff)' }) validate(@Param('bookingRef') ref: string, @Body('validatorId') validatorId: string) { return this.service.validate(ref, validatorId); } + + @Get(':bookingRef') + @ApiOperation({ summary: 'Get ticket by booking reference' }) + getByRef(@Param('bookingRef') ref: string) { + return this.service.getByRef(ref); + } + + @Post(':bookingRef/validate') + @ApiOperation({ summary: 'Validate ticket at gate (staff)' }) + validate( + @Param('bookingRef') ref: string, + @Body('validatorId') validatorId: string, + @Body('gateId') gateId?: string + ) { + return this.service.validate(ref, validatorId, gateId); + } + + @Get(':ticketId/validation-logs') + @ApiOperation({ summary: 'Get validation logs for ticket' }) + getValidationLogs(@Param('ticketId') ticketId: string) { + return this.service.getValidationLogs(ticketId); + } + + @Get('offline/export') + @ApiOperation({ summary: 'Export tickets for offline validation' }) + exportOfflineData(@Query('scheduleId') scheduleId: string) { + return this.service.exportOfflineData(scheduleId); + } + + @Post('validate/offline') + @ApiOperation({ summary: 'Batch import offline validations' }) + validateOfflineBatch(@Body() body: { validations: any[] }) { + return this.service.validateOfflineBatch(body.validations); + } } diff --git a/apps/edr-passenger-api/src/modules/tickets/tickets.service.spec.ts b/apps/edr-passenger-api/src/modules/tickets/tickets.service.spec.ts new file mode 100644 index 000000000..7f6ad7f52 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/tickets/tickets.service.spec.ts @@ -0,0 +1,126 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TicketsService } from './tickets.service'; +import { PrismaService } from '../../common/prisma.service'; + +describe('TicketsService - Offline Validation', () => { + let service: TicketsService; + let prisma: PrismaService; + + const mockPrisma = { + booking: { + findMany: jest.fn(), + findUnique: jest.fn(), + }, + ticket: { + findUnique: jest.fn(), + update: jest.fn(), + }, + gateValidationLog: { + create: jest.fn(), + }, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TicketsService, + { provide: PrismaService, useValue: mockPrisma }, + ], + }).compile(); + + service = module.get(TicketsService); + prisma = module.get(PrismaService); + jest.clearAllMocks(); + }); + + describe('exportOfflineData', () => { + it('should export tickets for offline validation', async () => { + const mockBookings = [ + { + bookingRef: 'ABC123', + ticket: { id: 'ticket-1', qrPayload: 'qr-data', validatedAt: null }, + seats: [{ passengerName: 'John Doe', seat: { label: '1A', coach: { label: 'A' } } }], + status: 'CONFIRMED', + }, + ]; + + mockPrisma.booking.findMany.mockResolvedValue(mockBookings); + + const result = await service.exportOfflineData('trip-1'); + + expect(result).toHaveLength(1); + expect(result[0].bookingRef).toBe('ABC123'); + expect(result[0].passengerName).toBe('John Doe'); + }); + }); + + describe('validateOfflineBatch', () => { + it('should process batch validations successfully', async () => { + const validations = [ + { + bookingRef: 'ABC123', + validatorId: 'validator-1', + gateId: 'gate-1', + validatedAt: new Date().toISOString(), + }, + ]; + + mockPrisma.booking.findUnique.mockResolvedValue({ id: 'booking-1' }); + mockPrisma.ticket.findUnique.mockResolvedValue({ id: 'ticket-1', validatedAt: null }); + mockPrisma.ticket.update.mockResolvedValue({}); + mockPrisma.gateValidationLog.create.mockResolvedValue({}); + + const result = await service.validateOfflineBatch(validations); + + expect(result.success).toBe(1); + expect(result.failed).toBe(0); + expect(result.duplicate).toBe(0); + }); + + it('should detect duplicate validations', async () => { + const validations = [ + { + bookingRef: 'ABC123', + validatorId: 'validator-1', + validatedAt: new Date().toISOString(), + }, + { + bookingRef: 'ABC123', + validatorId: 'validator-1', + validatedAt: new Date().toISOString(), + }, + ]; + + mockPrisma.booking.findUnique.mockResolvedValue({ id: 'booking-1' }); + mockPrisma.ticket.findUnique.mockResolvedValue({ id: 'ticket-1', validatedAt: null }); + mockPrisma.ticket.update.mockResolvedValue({}); + mockPrisma.gateValidationLog.create.mockResolvedValue({}); + + const result = await service.validateOfflineBatch(validations); + + expect(result.success).toBe(1); + expect(result.duplicate).toBe(1); + }); + + it('should handle already validated tickets', async () => { + const validations = [ + { + bookingRef: 'ABC123', + validatorId: 'validator-1', + validatedAt: new Date().toISOString(), + }, + ]; + + mockPrisma.booking.findUnique.mockResolvedValue({ id: 'booking-1' }); + mockPrisma.ticket.findUnique.mockResolvedValue({ + id: 'ticket-1', + validatedAt: new Date(), + }); + + const result = await service.validateOfflineBatch(validations); + + expect(result.duplicate).toBe(1); + expect(result.success).toBe(0); + }); + }); +}); diff --git a/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts b/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts index 1c9e05830..33971d3ec 100644 --- a/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts +++ b/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts @@ -2,6 +2,13 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm import { PrismaService } from '../../common/prisma.service'; import * as QRCode from 'qrcode'; +interface OfflineValidation { + bookingRef: string; + validatorId: string; + gateId?: string; + validatedAt: string; +} + @Injectable() export class TicketsService { constructor(private prisma: PrismaService) {} @@ -9,35 +16,135 @@ export class TicketsService { async generate(bookingId: string) { const booking = await this.prisma.booking.findUnique({ where: { id: bookingId }, - include: { trip: { include: { originStation: true, destinationStation: true, service: true } }, seats: { include: { seat: { include: { coach: true } } } } }, + include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: true } } } } }, }); if (!booking) throw new NotFoundException('Booking not found'); const qrPayload = await QRCode.toDataURL(`edr:tkt:${booking.id}:${booking.bookingRef}`); - return this.prisma.ticket.upsert({ where: { bookingId }, update: { qrPayload }, create: { bookingId, bookingRef: booking.bookingRef, qrPayload } }); + const barcodePayload = `EDR${booking.bookingRef}${booking.id.substring(0, 8).toUpperCase()}`; + return this.prisma.ticket.upsert({ + where: { bookingId }, + update: { qrPayload, barcodePayload }, + create: { bookingId, bookingRef: booking.bookingRef, qrPayload, barcodePayload } + }); } async getByRef(bookingRef: string) { const booking = await this.prisma.booking.findUnique({ where: { bookingRef }, - include: { trip: { include: { originStation: true, destinationStation: true, service: true } }, seats: { include: { seat: { include: { coach: true } } } }, ticket: true }, + include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: true } } } }, ticket: true }, }); if (!booking?.ticket) throw new NotFoundException('Ticket not found'); const seat = booking.seats[0]; return { id: booking.ticket.id, bookingId: booking.id, bookingRef: booking.bookingRef, status: booking.status, - fromStationName: booking.trip.originStation.name, toStationName: booking.trip.destinationStation.name, - departureAt: booking.trip.departureAt, trainName: booking.trip.service.name, + fromStationName: booking.schedule.originStation.name, toStationName: booking.schedule.destinationStation.name, + departureAt: booking.schedule.departureAt, trainName: booking.schedule.train.name, coachLabel: seat?.seat.coach.label, seatLabel: seat?.seat.label, passengerName: seat?.passengerName, priceMinor: booking.totalMinor, currency: booking.currency, qrPayload: booking.ticket.qrPayload, + barcodePayload: booking.ticket.barcodePayload }; } - async validate(bookingRef: string, validatorId: string) { + async validate(bookingRef: string, validatorId: string, gateId?: string) { const booking = await this.prisma.booking.findUnique({ where: { bookingRef } }); if (!booking) throw new NotFoundException('Booking not found'); const ticket = await this.prisma.ticket.findUnique({ where: { bookingId: booking.id } }); if (!ticket) throw new NotFoundException('Ticket not found'); - if (ticket.validatedAt) throw new BadRequestException('Ticket already validated'); - return this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: new Date(), validatorId } }); + if (ticket.validatedAt) { + await this.prisma.gateValidationLog.create({ + data: { ticketId: ticket.id, validatorId, gateId, status: 'REJECTED', reason: 'ALREADY_VALIDATED' } + }); + throw new BadRequestException('Ticket already validated'); + } + await this.prisma.ticket.update({ where: { id: ticket.id }, data: { validatedAt: new Date(), validatorId } }); + await this.prisma.gateValidationLog.create({ + data: { ticketId: ticket.id, validatorId, gateId, status: 'APPROVED' } + }); + return { validated: true, ticketId: ticket.id, validatedAt: new Date() }; + } + + async getValidationLogs(ticketId: string) { + return this.prisma.gateValidationLog.findMany({ + where: { ticketId }, + orderBy: { validatedAt: 'desc' } + }); + } + + async exportOfflineData(tripId: string) { + const bookings = await this.prisma.booking.findMany({ + where: { scheduleId: tripId, status: 'CONFIRMED' }, + include: { + ticket: true, + seats: { include: { seat: { include: { coach: true } } } }, + passenger: { include: { user: true } }, + }, + }); + + return bookings.map((b) => ({ + bookingRef: b.bookingRef, + ticketId: b.ticket?.id, + passengerName: b.seats[0]?.passengerName, + seatLabel: b.seats[0]?.seat.label, + coachLabel: b.seats[0]?.seat.coach.label, + qrPayload: b.ticket?.qrPayload, + status: b.status, + validatedAt: b.ticket?.validatedAt, + })); + } + + async validateOfflineBatch(validations: OfflineValidation[]) { + const results = { success: 0, failed: 0, duplicate: 0, errors: [] as string[] }; + const processedRefs = new Set(); + + for (const v of validations) { + if (processedRefs.has(v.bookingRef)) { + results.duplicate++; + continue; + } + processedRefs.add(v.bookingRef); + + try { + const booking = await this.prisma.booking.findUnique({ where: { bookingRef: v.bookingRef } }); + if (!booking) { + results.failed++; + results.errors.push(`Booking ${v.bookingRef} not found`); + continue; + } + + const ticket = await this.prisma.ticket.findUnique({ where: { bookingId: booking.id } }); + if (!ticket) { + results.failed++; + results.errors.push(`Ticket for ${v.bookingRef} not found`); + continue; + } + + if (ticket.validatedAt) { + results.duplicate++; + continue; + } + + await this.prisma.ticket.update({ + where: { id: ticket.id }, + data: { validatedAt: new Date(v.validatedAt), validatorId: v.validatorId }, + }); + + await this.prisma.gateValidationLog.create({ + data: { + ticketId: ticket.id, + validatorId: v.validatorId, + gateId: v.gateId, + status: 'APPROVED', + validatedAt: new Date(v.validatedAt), + }, + }); + + results.success++; + } catch (err) { + results.failed++; + results.errors.push(`Error processing ${v.bookingRef}: ${err instanceof Error ? err.message : String(err)}`); + } + } + + return results; } } diff --git a/apps/edr-passenger-api/src/modules/verifayda/verifayda.module.ts b/apps/edr-passenger-api/src/modules/verifayda/verifayda.module.ts new file mode 100644 index 000000000..c14ba3f1f --- /dev/null +++ b/apps/edr-passenger-api/src/modules/verifayda/verifayda.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { VerifaydaService } from './verifayda.service'; +import { PrismaModule } from '../../common/prisma.module'; + +@Module({ + imports: [PrismaModule], + providers: [VerifaydaService], + exports: [VerifaydaService], +}) +export class VerifaydaModule {} diff --git a/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts new file mode 100644 index 000000000..d86ad98a2 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts @@ -0,0 +1,142 @@ +import { Injectable, Logger, BadRequestException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PrismaService } from '../../common/prisma.service'; +import axios, { AxiosInstance } from 'axios'; + +export interface VerifaydaPassengerData { + fullName: string; + dateOfBirth: Date; + gender?: string; + nationality?: string; + profileData?: Record; +} + +export interface VerifaydaVerificationResult { + verified: boolean; + passengerData?: VerifaydaPassengerData; + failureReason?: string; +} + +@Injectable() +export class VerifaydaService { + private readonly logger = new Logger(VerifaydaService.name); + private readonly httpClient: AxiosInstance; + private readonly enabled: boolean; + private readonly apiUrl: string; + private readonly apiKey: string; + + constructor( + private readonly config: ConfigService, + private readonly prisma: PrismaService, + ) { + this.enabled = this.config.get('VERIFAYDA_ENABLED', false); + this.apiUrl = this.config.get('VERIFAYDA_API_URL', 'https://api.verifayda.gov.et/v2'); + this.apiKey = this.config.get('VERIFAYDA_API_KEY', ''); + + this.httpClient = axios.create({ + baseURL: this.apiUrl, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiKey, + }, + }); + } + + async verifyNationalId( + nationalId: string, + bookingId?: string, + ): Promise { + if (!this.enabled) { + this.logger.warn('Verifayda is disabled - skipping verification'); + return { + verified: false, + failureReason: 'Verifayda integration is disabled', + }; + } + + const requestPayload = { + nationalId, + requestedFields: ['fullName', 'dateOfBirth', 'gender', 'nationality'], + timestamp: new Date().toISOString(), + }; + + try { + this.logger.log(`Verifying national ID via Verifayda 2.0`); + + const response = await this.httpClient.post('/verify', requestPayload); + + const { data } = response; + + if (data.status === 'verified' && data.citizen) { + const passengerData: VerifaydaPassengerData = { + fullName: data.citizen.fullName, + dateOfBirth: new Date(data.citizen.dateOfBirth), + gender: data.citizen.gender, + nationality: data.citizen.nationality || 'Ethiopian', + profileData: data.citizen, + }; + + await this.prisma.verifaydaVerification.create({ + data: { + bookingId, + nationalId, + requestPayload, + responsePayload: data, + verified: true, + verifiedAt: new Date(), + }, + }); + + this.logger.log('Verifayda verification successful'); + + return { + verified: true, + passengerData, + }; + } else { + const failureReason = data.message || 'Verification failed'; + + await this.prisma.verifaydaVerification.create({ + data: { + bookingId, + nationalId, + requestPayload, + responsePayload: data, + verified: false, + failureReason, + }, + }); + + this.logger.warn(`Verifayda verification failed: ${failureReason}`); + + return { + verified: false, + failureReason, + }; + } + } catch (error: any) { + const errorMessage = error.response?.data?.message || error.message || 'Unknown error'; + + await this.prisma.verifaydaVerification.create({ + data: { + bookingId, + nationalId, + requestPayload, + verified: false, + failureReason: errorMessage, + }, + }); + + this.logger.error(`Verifayda API error: ${errorMessage}`); + + throw new BadRequestException( + `National ID verification failed: ${errorMessage}`, + ); + } + } + + isEnabled(): boolean { + return this.enabled; + } +} diff --git a/apps/edr-passenger-web/portal/.env.example b/apps/edr-passenger-web/portal/.env.example index 34eff7170..1fad0847d 100644 --- a/apps/edr-passenger-web/portal/.env.example +++ b/apps/edr-passenger-web/portal/.env.example @@ -1 +1 @@ -VITE_API_URL=http://localhost:3002 +VITE_API_URL=http://localhost:4000 diff --git a/apps/edr-passenger-web/portal/src/services/stations.service.ts b/apps/edr-passenger-web/portal/src/services/stations.service.ts index 542ae0e32..1c1b4f4d2 100644 --- a/apps/edr-passenger-web/portal/src/services/stations.service.ts +++ b/apps/edr-passenger-web/portal/src/services/stations.service.ts @@ -1,10 +1,13 @@ -import type { Passenger } from "@edr/types"; - +import type { IStation } from "../types"; import { api } from "../utils/api"; export const stationsService = { - list: async (): Promise => { + list: async (): Promise => { const { data } = await api.get("/stations"); return data.data; }, + get: async (id: string): Promise => { + const { data } = await api.get(`/stations/${id}`); + return data.data; + }, }; diff --git a/apps/edr-passenger-web/portal/src/types/index.ts b/apps/edr-passenger-web/portal/src/types/index.ts index ac3b23830..09b7c9fc2 100644 --- a/apps/edr-passenger-web/portal/src/types/index.ts +++ b/apps/edr-passenger-web/portal/src/types/index.ts @@ -1,4 +1,266 @@ -export type { Passenger } from "@edr/types"; +// โ”€โ”€ Enums โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export type TripStatus = 'SCHEDULED' | 'BOARDING' | 'EN_ROUTE' | 'ARRIVED' | 'CANCELLED' | 'DELAYED'; +export type SeatStatus = 'AVAILABLE' | 'HELD' | 'BOOKED' | 'BLOCKED'; +export type ServiceClass = 'ECONOMY' | 'BUSINESS' | 'FIRST'; +export type BookingStatus = 'DRAFT' | 'PENDING_PAYMENT' | 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NO_SHOW'; +export type PaymentMethod = 'TELEBIRR' | 'CBE_BIRR' | 'EBIRR' | 'CARD' | 'WALLET'; + +// โ”€โ”€ Station โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IStation { + id: string; + code: string; + name: string; + city: string; + timezone: string; + lat: number; + lng: number; +} + +// โ”€โ”€ Trip / Schedule โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ITripStation { + id: string; + code: string; + name: string; + city: string; +} + +export interface ITrip { + id: string; + number: string; + origin: ITripStation; + destination: ITripStation; + departureAt: string; + arrivalAt: string; + status: TripStatus; + availability: { ECONOMY: number; BUSINESS: number; FIRST: number }; + fares: { ECONOMY: number; BUSINESS: number; FIRST: number }; +} + +export interface ITripDetail { + id: string; + serviceId: string; + originStationId: string; + destinationStationId: string; + departureAt: string; + arrivalAt: string; + durationMinutes: number; + status: TripStatus; + service: { id: string; number: string; name: string }; + originStation: IStation; + destinationStation: IStation; + coaches: ICoach[]; + stopTimes: IStopTime[]; +} + +export interface IStopTime { + id: string; + sequence: number; + plannedArrivalAt: string | null; + plannedDepartureAt: string | null; + actualArrivalAt: string | null; + status: string; + station: IStation; +} + +// โ”€โ”€ Seat โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ISeat { + id: string; + number: string; // label from API + status: SeatStatus; + kind: string; +} + +export interface ICoach { + id: string; + name: string; + type: ServiceClass; + seats: ISeat[]; +} + +export interface ISeatMap { + coaches: ICoach[]; +} + +// โ”€โ”€ Segment-based seats โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ISegment { + fromStationId: string; + toStationId: string; + fromSequence: number; + toSequence: number; + fromName: string; + toName: string; +} + +export interface ISegmentSeat { + id: string; + label: string; + coach: string; + serviceClass: ServiceClass; + row: number; + col: string; +} + +export interface ISegmentAvailability { + segments: ISegment[]; + availableSeats: ISegmentSeat[]; + totalAvailable: number; +} + +export interface ISegmentHoldResult { + holdId: string; + expiresAt: string; + segments: ISegment[]; + seats: string[]; +} + +export interface ISegmentConfirmResult { + bookingId: string; + confirmedSeats: string[]; + segments: Array<{ fromStationId: string; toStationId: string; fromSequence: number; toSequence: number }>; +} + +// โ”€โ”€ Booking โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IBooking { + id: string; + bookingRef: string; + status: BookingStatus; + totalFare: number; + createdAt: string; + trip: { + number: string; + origin: ITripStation; + destination: ITripStation; + departureAt: string; + arrivalAt: string; + }; + passengers: Array<{ + fullName: string; + seat: { number: string; coach: string; class: ServiceClass }; + }>; + payment?: { method: PaymentMethod; status: string }; +} + +// โ”€โ”€ Ticket โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ITicket { + id: string; + bookingId: string; + bookingRef: string; + status: string; + fromStationName: string; + toStationName: string; + departureAt: string; + trainName: string; + coachLabel: string; + seatLabel: string; + passengerName: string; + priceMinor: number; + currency: string; + qrPayload: string; +} + +// โ”€โ”€ Fare quote โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IFareQuote { + tripId: string; + serviceClass: ServiceClass; + passengerCount: number; + baseFareMinor: number; + discountMinor: number; + loyaltyRedemptionMinor: number; + taxesFeesMinor: number; + totalMinor: number; + currency: string; +} + +// โ”€โ”€ Passenger โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IPassengerProfile { + id: string; + fullName: string; + email: string; + phone: string; + createdAt: string; + bookings: IBooking[]; +} + +// โ”€โ”€ Loyalty โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ILoyaltyAccount { + id: string; + passengerId: string; + pointsBalance: number; + tier: 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM'; + nextTier: string | null; + pointsToNextTier: number; + tierProgressPercent: number; +} + +// โ”€โ”€ Wallet โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IWallet { + id: string; + passengerId: string; + balanceMinor: number; + currency: string; +} + +// โ”€โ”€ Notification โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface INotification { + id: string; + passengerId: string; + title: string; + body: string; + category: string; + read: boolean; + deepLink?: string; + createdAt: string; +} + +// โ”€โ”€ Promotion โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IPromotion { + id: string; + title: string; + subtitle?: string; + code: string; + percentOff?: number; + amountOffMinor?: number; + validUntil: string; + ctaLabel?: string; + active: boolean; +} + +// โ”€โ”€ Live tracking โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface ILiveStatus { + tripId: string; + trainName: string; + fromStationName: string; + toStationName: string; + state: string; + currentLocationLabel?: string; + progressPercent: number; + delayMinutes: number; + currentSpeedKph?: number; + platformLabel?: string; + nextStopStationName?: string; + updatedAt: string; +} + +// โ”€โ”€ Dashboard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +export interface IDashboard { + user: { firstName: string; greetingKey: 'MORNING' | 'AFTERNOON' | 'EVENING' }; + upcomingTicket: { + ticketId?: string; + bookingRef: string; + from: string; + to: string; + trainName: string; + coachLabel?: string; + seatLabel?: string; + departureAt: string; + punctualityLabel: 'ON_TIME' | 'DELAYED'; + } | null; + wallet: { balanceMinor: number; currency: string } | null; + activePromotionsCount: number; + weatherAlerts: Array<{ id: string; title: string; message: string; severity: string }>; + stationSignals: Array<{ stationId: string; stationName: string; level: string; statusLabel: string }>; + savedRoutes: Array<{ id: string; fromName: string; toName: string; tripCount: number }>; +} export interface NavItem { href: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9f429ae8..c4cc0af18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -275,6 +275,9 @@ importers: apps/edr-passenger-api: dependencies: + '@nestjs/axios': + specifier: ^4.0.1 + version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.16.0)(rxjs@7.8.2) '@nestjs/common': specifier: ^11.0.0 version: 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -302,12 +305,12 @@ importers: '@nestjs/swagger': specifier: ^7.4.0 version: 7.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) - '@prisma/client': - specifier: ^5.8.0 - version: 5.22.0(prisma@5.22.0) '@sendgrid/mail': specifier: ^8.1.0 version: 8.1.6 + axios: + specifier: ^1.7.7 + version: 1.16.0 bcrypt: specifier: ^5.1.1 version: 5.1.1 @@ -354,6 +357,9 @@ importers: '@nestjs/testing': specifier: ^11.1.19 version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19) + '@prisma/client': + specifier: ^6.19.3 + version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3) '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 @@ -376,8 +382,8 @@ importers: specifier: ^29.7.0 version: 29.7.0(@types/node@20.19.41)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) prisma: - specifier: ^5.8.0 - version: 5.22.0 + specifier: ^6.19.3 + version: 6.19.3(typescript@5.9.3) supertest: specifier: ^7.0.0 version: 7.2.2 @@ -554,7 +560,7 @@ importers: version: 7.8.2 typeorm: specifier: ^0.3.20 - version: 0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) + version: 0.3.29(babel-plugin-macros@3.1.0)(mysql2@3.15.3)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) typescript: specifier: ^5.5.4 version: 5.9.3 @@ -1175,15 +1181,10 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@faker-js/faker@10.4.0': - resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} - engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - - '@fast-csv/format@4.3.5': - resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} - - '@fast-csv/parse@4.3.6': - resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -1373,12 +1374,6 @@ packages: '@types/node': optional: true - '@internationalized/date@3.12.0': - resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} - - '@internationalized/number@3.6.5': - resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1812,11 +1807,6 @@ packages: peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/jwt@11.0.2': - resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - '@nestjs/mapped-types@2.0.5': resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} peerDependencies: @@ -1830,19 +1820,6 @@ packages: class-validator: optional: true - '@nestjs/mapped-types@2.1.1': - resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==} - peerDependencies: - '@nestjs/common': ^10.0.0 || ^11.0.0 - class-transformer: ^0.4.0 || ^0.5.0 - class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - '@nestjs/microservices@11.1.19': resolution: {integrity: sha512-3Oja56ydTlSaui19/i7gYM0MMqz/w4UR2aqZeL4K8B+Fq0Ztg3zHb8et76atToJGpSCevJLEsoEMOMaGgzRwfg==} peerDependencies: @@ -1885,12 +1862,6 @@ packages: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 - '@nestjs/passport@11.0.5': - resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==} - peerDependencies: - '@nestjs/common': ^10.0.0 || ^11.0.0 - passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - '@nestjs/platform-express@11.1.19': resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} peerDependencies: @@ -1912,23 +1883,6 @@ packages: prettier: optional: true - '@nestjs/swagger@11.4.2': - resolution: {integrity: sha512-aBihEogDMj/bLEcaqhkvyX/ZVWUw/bmnhKzR0zwUoyGJikvZyaq7rOPYl/H7Lxkkr3c90SJxyuv1AX2UT1WKlw==} - peerDependencies: - '@fastify/static': ^8.0.0 || ^9.0.0 - '@nestjs/common': ^11.0.1 - '@nestjs/core': ^11.0.1 - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - '@fastify/static': - optional: true - class-transformer: - optional: true - class-validator: - optional: true - '@nestjs/swagger@7.4.2': resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==} peerDependencies: @@ -1959,26 +1913,6 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/throttler@6.5.0': - resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==} - peerDependencies: - '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - reflect-metadata: ^0.1.13 || ^0.2.0 - - '@nestjs/typeorm@11.0.1': - resolution: {integrity: sha512-8rw/nKT0S+L+MkzgE9F2/mox7mAgsPlwfzmW9gsESN1lmQtIrVEfiiBwC2O8+guS1jBfQehJIdcdUj2OAp4VUQ==} - peerDependencies: - '@nestjs/common': ^10.0.0 || ^11.0.0 - '@nestjs/core': ^10.0.0 || ^11.0.0 - reflect-metadata: ^0.1.13 || ^0.2.0 - rxjs: ^7.2.0 - typeorm: ^0.3.0 || ^1.0.0-dev - - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -2012,46 +1946,43 @@ packages: '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} - '@phc/format@1.0.0': - resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} - engines: {node: '>=10'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - - '@prisma/client@5.22.0': - resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} - engines: {node: '>=16.13'} + '@prisma/client@6.19.3': + resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==} + engines: {node: '>=18.18'} peerDependencies: prisma: '*' + typescript: '>=5.1.0' peerDependenciesMeta: prisma: optional: true + typescript: + optional: true - '@prisma/debug@5.22.0': - resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + '@prisma/config@6.19.3': + resolution: {integrity: sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==} - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': - resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + '@prisma/debug@6.19.3': + resolution: {integrity: sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==} - '@prisma/engines@5.22.0': - resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': + resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==} - '@prisma/fetch-engine@5.22.0': - resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + '@prisma/engines@6.19.3': + resolution: {integrity: sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==} - '@prisma/get-platform@5.22.0': - resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@prisma/fetch-engine@6.19.3': + resolution: {integrity: sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==} - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@prisma/get-platform@6.19.3': + resolution: {integrity: sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==} - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@remix-run/router@1.23.2': + resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} + engines: {node: '>=14.0.0'} '@radix-ui/react-accordion@1.2.12': resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} @@ -7181,218 +7112,225 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} - js-md5@0.8.3: - resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - jsdom@25.0.1: - resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^2.11.2 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-loader@0.5.7: - resolution: {integrity: sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json-stream@1.0.0: - resolution: {integrity: sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json3@3.3.3: - resolution: {integrity: sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==} - - json5@0.5.1: - resolution: {integrity: sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==} - hasBin: true - - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jsonwebtoken@9.0.3: - resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} - engines: {node: '>=12', npm: '>=6'} - - jspdf@3.0.4: - resolution: {integrity: sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==} - - jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - - jwa@1.4.2: - resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} - - jwa@2.0.1: - resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - - jws@3.2.3: - resolution: {integrity: sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==} - - jws@4.0.1: - resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - killable@1.0.1: - resolution: {integrity: sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==} - - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - - kind-of@4.0.0: - resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} - engines: {node: '>=0.10.0'} - - kind-of@5.1.0: - resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} - engines: {node: '>=0.10.0'} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - lazy-cache@1.0.4: - resolution: {integrity: sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==} - engines: {node: '>=0.10.0'} - - lazy-cache@2.0.2: - resolution: {integrity: sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA==} - engines: {node: '>=0.10.0'} - - lazystream@1.0.1: - resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} - engines: {node: '>= 0.6.3'} - - lcid@1.0.0: - resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==} - engines: {node: '>=0.10.0'} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libphonenumber-js@1.13.1: - resolution: {integrity: sha512-GEw0GLL7YUUA6nv21IsCvVjtI5Ejn84sjbdfQ9KxdbqEVOk1PZh7xejn01EEiniKw+dBeCfim+8MGeuvVuE2BA==} - - libreoffice-convert@1.8.1: - resolution: {integrity: sha512-iZ1DD/EMTlPvol8G++QQ/0w4pVecSwRuhMLXRm7nRim/gcaSscSXuTO9Tgbkieyw5UdJg7UXD+lkFT8SCi51Dw==} - engines: {node: '>=6'} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.60.3': + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.3': + resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.3': + resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.3': + resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.3': + resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.3': + resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.3': + resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.3': + resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.3': + resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.3': + resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.3': + resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.3': + resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.3': + resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.3': + resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.3': + resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.3': + resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.3': + resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + + '@sendgrid/client@8.1.6': + resolution: {integrity: sha512-/BHu0hqwXNHr2aLhcXU7RmmlVqrdfrbY9KpaNj00KZHlVOVoRxRVrpOCabIB+91ISXJ6+mLM9vpaVUhK6TwBWA==} + engines: {node: '>=12.*'} + + '@sendgrid/helpers@8.0.0': + resolution: {integrity: sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==} + engines: {node: '>= 12.0.0'} + + '@sendgrid/mail@8.1.6': + resolution: {integrity: sha512-/ZqxUvKeEztU9drOoPC/8opEPOk+jLlB2q4+xpx6HVLq6aFu3pMpalkTpAQz8XfRfpLp8O25bh6pGPcHDCYpqg==} + engines: {node: '>=12.*'} + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@sqltools/formatter@1.2.5': + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tanstack/query-core@5.100.10': + resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} + + '@tanstack/react-query@5.100.10': + resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} + peerDependencies: + react: 18.3.1 + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@turbo/darwin-64@2.9.12': + resolution: {integrity: sha512-eu3eFRmE9NjgZ0wPdRJ44l+LGSeIky+tz5ZQd8zQkw/Yqi+BM7wq+8nbabeoiVUcICi/IZweMOKl/MCmkrd1+g==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.12': + resolution: {integrity: sha512-RUkAE404z/J8NsyrUosMcBaXT6M4bRFxTQrmkDQBLQVXaC8Jl0e9bMvYDSX0GW7Ffm2m3j9y7RXgR1foeUAM9w==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.12': + resolution: {integrity: sha512-InIUtH7cw/vqXNX1Gr7QgWfmw3ct08pV5CpfdEOR48z2u2rzdmpIuk00B/Q2xCb0PMWtKgiMQynfuphmEuUyTQ==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.12': + resolution: {integrity: sha512-lC6nD//Xh67fmJM0LKaLsg74Wry0aYrgMklpiNgCbUaMdPIOqj0A00iri3NU7Lb7pZHx8ViisgpeDKlpSgFUCA==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.12': + resolution: {integrity: sha512-conYri8VUl72JOdYnLDPYwzqbPcY5ECoHmo9FWoKznemhaAIilj4maHqs9Uar0aKfNoZIULniy+6iWaLtLO34A==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.12': + resolution: {integrity: sha512-XoR4bsg62/L/esRVcmoMESEiNZ36+YmyjYGLpoqk8nwMgXzzVjNOgX0lRSz5w/U/ajLGv3nhMsS0Q2QOdvp2AQ==} + cpu: [arm64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/conventional-commits-parser@5.0.2': + resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} + + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -7421,250 +7359,263 @@ packages: os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - linebreak@1.1.0: - resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - linkifyjs@4.3.2: - resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - lint-staged@15.5.2: - resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} - engines: {node: '>=18.12.0'} + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + + '@types/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.17': + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@6.0.3': + resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typescript-eslint/eslint-plugin@8.59.3': + resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.3 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.3': + resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.59.3': + resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.59.3': + resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.59.3': + resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.59.3': + resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.59.3': + resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.3': + resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.59.3': + resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.59.3': + resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true - listenercount@1.0.1: - resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - listr2@8.3.3: - resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} - engines: {node: '>=18.0.0'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} - load-esm@1.0.3: - resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} - engines: {node: '>=13.2.0'} - - load-json-file@1.1.0: - resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} - engines: {node: '>=0.10.0'} - - load-json-file@2.0.0: - resolution: {integrity: sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==} - engines: {node: '>=4'} - - loadash@1.0.0: - resolution: {integrity: sha512-xlX5HBsXB3KG0FJbJJG/3kYWCfsCyCSus3T+uHVu6QL6YxAdggmm3QeyLgn54N2yi5/UE6xxL5ZWJAAiHzHYEg==} - deprecated: Package is unsupport. Please use the lodash package instead. - - loader-runner@2.4.0: - resolution: {integrity: sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==} - engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} - - loader-runner@4.3.2: - resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} - engines: {node: '>=6.11.5'} - - loader-utils@1.4.2: - resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} - engines: {node: '>=4.0.0'} - - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - locter@2.2.1: - resolution: {integrity: sha512-Cc7mowptFl7ug5he6Iuos7aGRd9xbwTfnx1ng4AX/7F4iqemPaXAIJDi13IBwQZrKgli9OPEYXm6uCKr7ynxUQ==} - engines: {node: '>=22.0.0'} - - lodash._reinterpolate@3.0.0: - resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.difference@4.5.0: - resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} - - lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - - lodash.flatten@4.4.0: - resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} - - lodash.groupby@4.6.0: - resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} - - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - - lodash.isfunction@3.0.9: - resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnil@4.0.0: - resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.isundefined@3.0.1: - resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} - - lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.mergewith@4.6.2: - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - - lodash.template@4.18.1: - resolution: {integrity: sha512-5urZrLnV/VD6zHK5KsVtZgt7H19v51mIzoS0aBNH8yp3I8tbswrEjOABOPY8m8uB7NuibubLrMX+Y0PXsU9X+w==} - deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. - - lodash.templatesettings@4.2.0: - resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} - - lodash.union@4.6.0: - resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash.upperfirst@4.3.1: - resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - log-ok@0.1.1: - resolution: {integrity: sha512-cc8VrkS6C+9TFuYAwuHpshrcrGRAv7d0tUJ0GdM72ZBlKXtlgjUZF84O+OhQUdiVHoF7U/nVxwpjOdwUJ8d3Vg==} - engines: {node: '>=0.10.0'} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} - - log-utils@0.2.1: - resolution: {integrity: sha512-udyegKoMz9eGfpKAX//Khy7sVAZ8b1F7oLDnepZv/1/y8xTvsyPgqQrM94eG8V0vcc2BieYI2kVW4+aa6m+8Qw==} - engines: {node: '>=0.10.0'} - - logging-helpers@1.0.0: - resolution: {integrity: sha512-qyIh2goLt1sOgQQrrIWuwkRjUx4NUcEqEGAcYqD8VOnOC6ItwkrVE8/tA4smGpjzyp4Svhc6RodDp9IO5ghpyA==} - engines: {node: '>=0.10.0'} - - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - - longest@1.0.1: - resolution: {integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==} - engines: {node: '>=0.10.0'} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lottie-web@5.13.0: - resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==} - - loud-rejection@1.6.0: - resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} - engines: {node: '>=0.10.0'} - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@11.3.6: - resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} - engines: {node: 20 || >=22} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lucide-react@0.513.0: - resolution: {integrity: sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg==} + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -7672,238 +7623,264 @@ packages: resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} engines: {node: '>=12'} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - make-cancellable-promise@2.0.0: - resolution: {integrity: sha512-3SEQqTpV9oqVsIWqAcmDuaNeo7yBO3tqPtqGRcKkEo0lrzD3wqbKG9mkxO65KoOgXqj+zH2phJ2LiAsdzlogSw==} - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - make-event-props@2.0.0: - resolution: {integrity: sha512-G/hncXrl4Qt7mauJEXSg3AcdYzmpkIITTNl5I+rH9sog5Yw0kK6vseJjCaPfOXqOqQuPUP89Rkhfz5kPS8ijtw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - map-cache@0.2.2: - resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} - engines: {node: '>=0.10.0'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-visit@1.0.0: - resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} - engines: {node: '>=0.10.0'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - - media-engine@1.0.3: - resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - - mem@1.1.0: - resolution: {integrity: sha512-nOBDrc/wgpkd3X/JOhMqYR+/eLqlfLP4oQfoBA6QExIxEl+GU01oyEkwWyueyO8110pUKijtiHGhEmYoOn88oQ==} - engines: {node: '>=4'} - - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} - - memory-fs@0.4.1: - resolution: {integrity: sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==} - - meow@12.1.1: - resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} - engines: {node: '>=16.10'} - - meow@3.7.0: - resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} - engines: {node: '>=0.10.0'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - merge-refs@2.0.0: - resolution: {integrity: sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 peerDependenciesMeta: - '@types/react': + ajv: optional: true - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 - micromatch@3.1.10: - resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} - engines: {node: '>=0.10.0'} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - miller-rabin@4.0.1: - resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} - hasBin: true + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - mime@1.2.11: - resolution: {integrity: sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - - mimic-fn@1.2.0: - resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} - engines: {node: '>=4'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + ansis@4.3.0: + resolution: {integrity: sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==} + engines: {node: '>=14'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + + axios@1.16.0: + resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minio@7.1.3: - resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==} - engines: {node: ^16 || ^18 || >=20} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} hasBin: true - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} - motion-dom@12.38.0: - resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - motion-utils@12.36.0: - resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} mui-ethiopian-datepicker@0.3.2: resolution: {integrity: sha512-I8TZ8lvloAxhFNl8tDl7NmgT7RPJslfnArNMpT2Sd9GbpfsdRrOwH/JBMcOhLQyaNUee9due0W6J85io1WLjlg==} @@ -7917,59 +7894,61 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 - multer@2.1.1: - resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} - engines: {node: '>= 10.16.0'} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} - multicast-dns-service-types@1.1.0: - resolution: {integrity: sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} - multicast-dns@7.2.5: - resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - nanomatch@1.2.13: - resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} - engines: {node: '>=0.10.0'} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} - negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - nestjs-minio-client@2.2.0: - resolution: {integrity: sha512-mz1vfJq/7YfSyVCIeZwOCfIfBz+msI9QynHS2QGO9GB+tVNnQOYta8PxFsH9tMxN7gNrjrf5jXsEIpgBB1oTeA==} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} peerDependencies: - '@nestjs/common': '>=9.0.0' - '@nestjs/core': '>=9.0.0' + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} @@ -7977,414 +7956,449 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - - node-addon-api@5.1.0: - resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - - node-addon-api@8.7.0: - resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} - engines: {node: ^18 || ^20 || >= 21} - - node-emoji@1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-forge@0.10.0: - resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==} - engines: {node: '>= 6.0.0'} - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - node-html-parser@6.1.13: - resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-libs-browser@2.2.1: - resolution: {integrity: sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==} - - node-releases@2.0.44: - resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - hasBin: true - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-path@2.1.1: - resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} - engines: {node: '>=0.10.0'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-svg-path@1.1.0: - resolution: {integrity: sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==} - - npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - number-is-nan@1.0.1: - resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} - engines: {node: '>=0.10.0'} - - nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - - oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-copy@0.1.0: - resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object-visit@1.0.1: - resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} - engines: {node: '>=0.10.0'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - on-headers@1.1.0: - resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + canvas@2.11.2: + resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} + engines: {node: '>=6'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - opencollective-postinstall@2.0.3: - resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} - hasBin: true - - opn@5.5.0: - resolution: {integrity: sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==} - engines: {node: '>=4'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - orderedmap@2.1.1: - resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - original@1.0.2: - resolution: {integrity: sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==} - - os-browserify@0.3.0: - resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} - - os-locale@1.4.0: - resolution: {integrity: sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==} - engines: {node: '>=0.10.0'} - - os-locale@2.1.0: - resolution: {integrity: sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==} - engines: {node: '>=4'} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} - p-map@1.2.0: - resolution: {integrity: sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==} - engines: {node: '>=4'} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + citty@0.2.2: + resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.4: + resolution: {integrity: sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} - pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-asn1@5.1.9: - resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} - engines: {node: '>= 0.10'} - - parse-json@2.2.0: - resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} - engines: {node: '>=0.10.0'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-svg-path@0.1.2: - resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - pascalcase@0.1.1: - resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} - engines: {node: '>=0.10.0'} - - passport-jwt@4.0.1: - resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} - - passport-strategy@1.0.0: - resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} - engines: {node: '>= 0.4.0'} - - passport@0.7.0: - resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} - engines: {node: '>= 0.4.0'} - - path-browserify@0.0.1: - resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==} - - path-dirname@1.0.2: - resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} - - path-exists@2.1.0: - resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} - engines: {node: '>=0.10.0'} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-is-inside@1.0.2: - resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} - - path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - path-to-regexp@0.1.13: - resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - path-type@1.1.0: - resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-json@5.0.0: + resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} + engines: {node: '>= 6'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + + conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + + conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron@4.4.0: + resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} + engines: {node: '>=18.x'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dargs@8.1.0: + resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} + engines: {node: '>=12'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - path-type@2.0.0: - resolution: {integrity: sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==} - engines: {node: '>=4'} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + decompress-response@4.2.1: + resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} engines: {node: '>=8'} + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + path@0.12.7: resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} + dotenv@17.4.1: + resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==} + engines: {node: '>=12'} - pause@0.0.1: - resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} - pbkdf2@3.1.5: - resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} - engines: {node: '>= 0.10'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} pdfjs-dist@2.16.105: resolution: {integrity: sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==} @@ -8398,344 +8412,358 @@ packages: resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==} engines: {node: '>=20.16.0 || >=22.3.0'} - peek-readable@5.4.2: - resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==} - engines: {node: '>=14.16'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - perfect-freehand@1.2.3: - resolution: {integrity: sha512-bHZSfqDHGNlPpgH2yxXgPHlQSPpEbo+qg7li0M78J9vNAi2yjwLeA4x79BEQhX44lEWpCLSFCeRZwpw0niiXPA==} + effect@3.21.0: + resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==} - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + electron-to-chromium@1.5.353: + resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.13.0: - resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} - engines: {node: '>= 16.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - pinkie-promise@2.0.1: - resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} - engines: {node: '>=0.10.0'} - - pinkie@2.0.4: - resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-dir@2.0.0: - resolution: {integrity: sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==} - engines: {node: '>=4'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - png-js@2.0.0: - resolution: {integrity: sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==} - - pngjs@5.0.0: - resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} - engines: {node: '>=10.13.0'} - - popper.js@1.16.1: - resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==} - deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1 - - portfinder@1.0.38: - resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} - engines: {node: '>= 10.12'} - - posix-character-classes@0.1.1: - resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} - engines: {node: '>=0.10.0'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.1.0: - resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.1: - resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} - hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - prisma@5.22.0: - resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} - engines: {node: '>=16.13'} - hasBin: true - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - prosemirror-changeset@2.4.1: - resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} - - prosemirror-commands@1.7.1: - resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} - - prosemirror-dropcursor@1.8.2: - resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} - - prosemirror-gapcursor@1.4.1: - resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} - - prosemirror-history@1.5.0: - resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} - - prosemirror-keymap@1.2.3: - resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} - - prosemirror-model@1.25.4: - resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} - - prosemirror-schema-list@1.5.1: - resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} - - prosemirror-state@1.4.4: - resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} - - prosemirror-tables@1.8.5: - resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} - - prosemirror-transform@1.12.0: - resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} - - prosemirror-view@1.41.8: - resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-compare@3.0.1: - resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} - - proxy-from-env@2.1.0: - resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} - engines: {node: '>=10'} - - proxy-memoize@3.0.1: - resolution: {integrity: sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==} - - prr@1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - public-encrypt@4.0.3: - resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} - - punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - qrcode@1.5.4: - resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} - engines: {node: '>=10.13.0'} - hasBin: true - - qs@6.15.1: - resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} - engines: {node: '>=0.6'} - - qs@6.5.5: - resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} - engines: {node: '>=0.6'} - - query-string@7.1.3: - resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} - engines: {node: '>=6'} - - querystring-es3@0.2.1: - resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} - engines: {node: '>=0.4.x'} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - - raf@3.4.1: - resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - - randomfill@1.0.4: - resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - rapiq@0.9.0: - resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} - - raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} + enhanced-resolve@5.21.3: + resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} + engines: {node: '>=10.13.0'} - react-cookie@8.1.2: - resolution: {integrity: sha512-S45Z1y1dHyYfLEI4bFKQICuP+SwJqTPWbdc2ZpE6aQSdjSVJAjUDfwTPq8B7BWieIsgyyEWMb/QOrudtwJMjXA==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.1.0: + resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} + engines: {node: '>=14.21.3'} peerDependencies: react: '>= 16.3.0' @@ -8825,14 +8853,17 @@ packages: react-dom: optional: true - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - react-is@19.2.6: - resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. react-joyride@2.9.3: resolution: {integrity: sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==} @@ -8853,8 +8884,9 @@ packages: '@react-pdf/renderer': '>=3.4.4' react: '>=16' - react-pdf-viewer@0.1.0: - resolution: {integrity: sha512-JO0bZEA6EMtljhTrLNyn9T03nIOXY6/tzK8guOdwfYpImdOpYfACy47G9AKXrVrtob9bEW2d1jR3MkC0NEGajw==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} react-pdf@10.4.1: resolution: {integrity: sha512-kS/35staVCBqS29verTQJQZXw7RfsRCPO3fdJoW1KXylcv7A9dw6DZ3vJXC2w+bIBgLw5FN4pOFvKSQtkQhPfA==} @@ -8878,9 +8910,9 @@ packages: redux: optional: true - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} @@ -8892,8 +8924,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -8978,9 +9010,9 @@ packages: resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} engines: {node: '>=0.10.0'} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + 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 react@19.2.6: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} @@ -8989,286 +9021,325 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read-pkg-up@1.0.1: - resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} - engines: {node: '>=0.10.0'} - - read-pkg-up@2.0.0: - resolution: {integrity: sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==} - engines: {node: '>=4'} - - read-pkg@1.1.0: - resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} - engines: {node: '>=0.10.0'} - - read-pkg@2.0.0: - resolution: {integrity: sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==} - engines: {node: '>=4'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - readable-web-to-node-stream@3.0.4: - resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - readdir-glob@1.1.3: - resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} - readdirp@2.2.1: - resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} - engines: {node: '>=0.10'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - recharts@3.8.1: - resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - redent@1.0.0: - resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==} - engines: {node: '>=0.10.0'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - - reflect-metadata@0.1.14: - resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - - regex-not@1.0.2: - resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} - engines: {node: '>=0.10.0'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - relative@3.0.2: - resolution: {integrity: sha512-Q5W2qeYtY9GbiR8z1yHNZ1DGhyjb4AnLEjt8iE6XfcC1QIu+FAtj3HQaO0wH28H1mX6cqNLvAqWhP402dxJGyA==} - engines: {node: '>= 0.8.0'} - - remarkable@1.7.4: - resolution: {integrity: sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==} - engines: {node: '>= 0.10.0'} - hasBin: true - - remove-trailing-separator@1.1.0: - resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - - repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - repeating@2.0.1: - resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==} - engines: {node: '>=0.10.0'} - - request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - require-main-filename@1.0.1: - resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} - - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} - - resolve-cwd@2.0.0: - resolution: {integrity: sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==} - engines: {node: '>=4'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@3.0.0: - resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} - engines: {node: '>=4'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-url@0.2.1: - resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated - - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} - engines: {node: '>= 0.4'} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - - restructure@3.0.2: - resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} - - ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rgbcolor@1.0.1: - resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} - engines: {node: '>= 0.8.15'} - - right-align@0.1.3: - resolution: {integrity: sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==} - engines: {node: '>=0.10.0'} - - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - ripemd160@2.0.3: - resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - rollup@4.60.3: - resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} hasBin: true - rope-sequence@1.3.4: - resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} - rrweb-cssom@0.7.1: - resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} - engines: {node: '>=0.4'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} - safe-regex@1.1.0: - resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} - engines: {node: '>=11.0.0'} + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} - saxes@5.0.1: - resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} scheduler@0.19.1: resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==} @@ -9276,8 +9347,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - scheduler@0.25.0-rc-603e6108-20241029: - resolution: {integrity: sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -9286,185 +9358,222 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - scroll@3.0.1: - resolution: {integrity: sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==} + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - scrollparent@2.1.0: - resolution: {integrity: sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==} + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true - select-hose@2.0.0: - resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} - - self-closing-tags@1.0.1: - resolution: {integrity: sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==} - engines: {node: '>=0.12.0'} - - selfsigned@1.10.14: - resolution: {integrity: sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} - engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - send@0.19.2: - resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} - engines: {node: '>= 0.8.0'} - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-index@1.9.2: - resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.3: - resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} - engines: {node: '>= 0.8.0'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-getter@0.1.1: - resolution: {integrity: sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==} - engines: {node: '>=0.10.0'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - sha.js@2.4.12: - resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} - engines: {node: '>= 0.10'} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - signature_pad@2.3.2: - resolution: {integrity: sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - slice-ansi@7.1.2: - resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} - engines: {node: '>=18'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - smob@1.6.1: - resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} - engines: {node: '>=20.0.0'} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - snapdragon-node@2.1.1: - resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} - engines: {node: '>=0.10.0'} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true - snapdragon-util@3.0.1: - resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} - engines: {node: '>=0.10.0'} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true - snapdragon@0.8.2: - resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} - engines: {node: '>=0.10.0'} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - socket.io-client@4.8.3: - resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} - engines: {node: '>=10.0.0'} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - socket.io-parser@4.2.6: - resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} - engines: {node: '>=10.0.0'} + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} - sockjs-client@1.1.5: - resolution: {integrity: sha512-PmPRkAYIeuRgX+ZSieViT4Z3Q23bLS2Itm/ck1tSf5P0/yVuFDiI5q9mcnpXoMdToaPSRS9MEyUx/aaBxrFzyw==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} - sockjs@0.3.19: - resolution: {integrity: sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} @@ -9472,880 +9581,783 @@ packages: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - source-list-map@2.0.1: - resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - source-map-resolve@0.5.3: - resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated + jws@3.2.3: + resolution: {integrity: sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==} - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - source-map-url@0.4.1: - resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated - - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} - - spdy-transport@3.0.0: - resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} - - spdy@4.0.2: - resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} - engines: {node: '>=6.0.0'} - - split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + libphonenumber-js@1.13.1: + resolution: {integrity: sha512-GEw0GLL7YUUA6nv21IsCvVjtI5Ejn84sjbdfQ9KxdbqEVOk1PZh7xejn01EEiniKw+dBeCfim+8MGeuvVuE2BA==} - sql-highlight@6.1.0: - resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} - ssf@0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} + lint-staged@15.5.2: + resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} + engines: {node: '>=18.12.0'} hasBin: true - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - stackblur-canvas@2.7.0: - resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} - engines: {node: '>=0.1.14'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - static-extend@0.1.2: - resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} - engines: {node: '>=0.10.0'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} - stream-browserify@2.0.2: - resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==} - - stream-http@2.8.3: - resolution: {integrity: sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==} - - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - - strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-width@1.0.2: - resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} - engines: {node: '>=0.10.0'} - - string-width@2.1.1: - resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} - engines: {node: '>=4'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} - strip-ansi@3.0.1: - resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} - engines: {node: '>=0.10.0'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} - strip-ansi@4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-bom@2.0.0: - resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} - engines: {node: '>=0.10.0'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-indent@1.0.1: - resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==} - engines: {node: '>=0.10.0'} + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} hasBin: true - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} - striptags@3.2.0: - resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} - strnum@1.1.2: - resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} - - strtok3@10.3.5: - resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - strtok3@7.1.1: - resolution: {integrity: sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==} - engines: {node: '>=16'} + mimic-response@2.1.0: + resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} + engines: {node: '>=8'} - style-object-to-css-string@1.1.3: - resolution: {integrity: sha512-bISQoUsir/qGfo7vY8rw00ia9nnyE1jvYt3zZ2jhdkcXZ6dAEi74inMzQ6On57vFI+I4Fck6wOv5UI9BEwJDgw==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - success-symbol@0.1.0: - resolution: {integrity: sha512-7S6uOTxPklNGxOSbDIg4KlVLBQw1UiGVyfCUYgYxrZUKRblUkmGj7r8xlfQoFudvqLv6Ap5gd76/IIFfI9JG2A==} - engines: {node: '>=0.10.0'} - - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - superagent@10.3.0: - resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} - engines: {node: '>=14.18.0'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - supertest@7.2.2: - resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} - engines: {node: '>=14.18.0'} - - supports-color@4.5.0: - resolution: {integrity: sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==} - engines: {node: '>=4'} - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svg-arc-to-cubic-bezier@3.2.0: - resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==} - - svg-pathdata@6.0.3: - resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} - engines: {node: '>=12.0.0'} - - swagger-ui-dist@5.17.14: - resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} - - swagger-ui-dist@5.32.4: - resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} - - swagger-ui-express@5.0.1: - resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} - engines: {node: '>= v0.10.32'} - peerDependencies: - express: '>=4.0.0 || >=5.0.0-beta' - - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - - tailwind-scrollbar-hide@4.0.0: - resolution: {integrity: sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ==} - peerDependencies: - tailwindcss: '>=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20' - - tailwindcss-animate@1.0.7: - resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' - - tailwindcss@3.4.19: - resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} - engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.3.0: - resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - tapable@0.2.9: - resolution: {integrity: sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==} - engines: {node: '>=0.6'} + multer@2.1.1: + resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} + engines: {node: '>= 10.16.0'} - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + mysql2@3.15.3: + resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} + engines: {node: '>= 8.0'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar 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 + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - terser-webpack-plugin@5.6.0: - resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} - engines: {node: '>= 10.13.0'} + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + + nan@2.26.2: + resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 + encoding: ^0.1.0 peerDependenciesMeta: - '@minify-html/node': + encoding: optional: true - '@swc/core': + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + nypm@0.6.6: + resolution: {integrity: sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==} + engines: {node: '>=18'} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: optional: true postcss: optional: true - uglify-js: + tsx: + optional: true + yaml: optional: true - terser@5.47.1: - resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} - engines: {node: '>=10'} - hasBin: true - - tesseract.js-core@7.0.0: - resolution: {integrity: sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==} - - tesseract.js@7.0.0: - resolution: {integrity: sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==} - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - text-extensions@2.4.0: - resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} - engines: {node: '>=8'} - - text-segmentation@1.0.3: - resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - - through2@4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - thunky@1.1.0: - resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - - time-stamp@1.1.0: - resolution: {integrity: sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==} - engines: {node: '>=0.10.0'} - - time-stamp@2.2.0: - resolution: {integrity: sha512-zxke8goJQpBeEgD82CXABeMh0LSJcj7CXEd0OHOg45HgcofF7pxNwZm9+RknpxpDhwN4gFpySkApKfFYfRQnUA==} - engines: {node: '>=0.10.0'} - - timers-browserify@2.0.12: - resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} - engines: {node: '>=0.6.0'} - - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} - engines: {node: '>=18'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - tinymce@7.9.2: - resolution: {integrity: sha512-zS2gn2CPQmZhUqLzkhwYH+WGsx/DIRY/mS18RVzsIcuQg2lN2uzaqoHSJU6DdMUpvXBtBFnLNpumC3QrDwLBzA==} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} - engines: {node: '>=14.14'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-arraybuffer@1.0.1: - resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} - - to-buffer@1.2.2: - resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} - engines: {node: '>= 0.4'} - - to-gfm-code-block@0.1.1: - resolution: {integrity: sha512-LQRZWyn8d5amUKnfR9A9Uu7x9ss7Re8peuWR2gkh1E+ildOfv2aF26JpuDg8JtvCduu5+hOrMIH+XstZtnagqg==} - engines: {node: '>=0.10.0'} - - to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - - to-regex-range@2.1.1: - resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} - engines: {node: '>=0.10.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - to-regex@3.0.2: - resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} - engines: {node: '>=0.10.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - token-types@5.0.1: - resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} - engines: {node: '>=14.16'} - - token-types@6.1.2: - resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} - engines: {node: '>=14.16'} - - tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - - traverse@0.3.9: - resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} - - tree-changes@0.11.3: - resolution: {integrity: sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==} - - tree-changes@0.9.3: - resolution: {integrity: sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==} - - trim-canvas@0.1.2: - resolution: {integrity: sha512-nd4Ga3iLFV94mdhW9JFMLpQbHUyCQuhFOD71PEAt1NjtMD5wbZctzhX8c3agHNybMR5zXD1XTGoIEWk995E6pQ==} - - trim-newlines@1.0.0: - resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} - engines: {node: '>=0.10.0'} - - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} peerDependencies: - typescript: '>=4.8.4' + postcss: ^8.2.14 - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - ts-jest@29.4.9: - resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 || ^30.0.0 - '@jest/types': ^29.0.0 || ^30.0.0 - babel-jest: ^29.0.0 || ^30.0.0 - esbuild: '*' - jest: ^29.0.0 || ^30.0.0 - jest-util: ^29.0.0 || ^30.0.0 - typescript: '>=4.3 <7' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true - - ts-loader@9.5.7: - resolution: {integrity: sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==} - engines: {node: '>=12.0.0'} - peerDependencies: - typescript: '*' - webpack: ^5.0.0 - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tsconfig-paths-webpack-plugin@4.2.0: - resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} - engines: {node: '>=10.13.0'} - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tty-browserify@0.0.0: - resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==} - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - turbo@2.9.12: - resolution: {integrity: sha512-lCPgus1NuTiBdaITWqzSH/Ff6HVL8HHGBtOXHg1dHRfcshN79XkygSdh0M6g8b0td91ILLG5MTkLOkp5UvyPJw==} - hasBin: true - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - - type@2.7.3: - resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typeof-article@0.1.1: - resolution: {integrity: sha512-Vn42zdX3FhmUrzEmitX3iYyLb+Umwpmv8fkZRIknYh84lmdrwqZA5xYaoKiIj2Rc5i/5wcDrpUmZcbk1U51vTw==} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - typeorm-extension@3.9.0: - resolution: {integrity: sha512-LCVo/7zEh59/+Ig+WsXFbuu1gvU7EilIOH1vwxTG2eAiKmteaTQlID9c1x/PQH2IyZ+Lk4htbWf1d9QieKrpPQ==} - engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@faker-js/faker': '>=8.4.1' - typeorm: ~0.3.0 - - typeorm@0.3.29: - resolution: {integrity: sha512-wwPEX/df4l72gCmOsrs0otJZYLGA9lLQkUZCkukbsymEycV4zXv2KM7wU7v2r8L01TaCgY9ApSSqHQWBOUhEoQ==} - engines: {node: '>=16.13.0'} - hasBin: true - peerDependencies: - '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@sap/hana-client': ^2.14.22 - better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 - ioredis: ^5.0.4 - mongodb: ^5.8.0 || ^6.0.0 - mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 - mysql2: ^2.2.5 || ^3.0.1 - oracledb: ^6.3.0 - pg: ^8.5.1 - pg-native: ^3.0.0 - pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 || ^5.0.14 - sql.js: ^1.4.0 - sqlite3: ^5.0.3 - ts-node: ^10.7.0 - typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 - peerDependenciesMeta: - '@google-cloud/spanner': - optional: true - '@sap/hana-client': - optional: true - better-sqlite3: - optional: true - ioredis: - optional: true - mongodb: - optional: true - mssql: - optional: true - mysql2: - optional: true - oracledb: - optional: true - pg: - optional: true - pg-native: - optional: true - pg-query-stream: - optional: true - redis: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - ts-node: - optional: true - typeorm-aurora-data-api-driver: - optional: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - uglify-js@2.8.29: - resolution: {integrity: sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==} - engines: {node: '>=0.8.0'} - hasBin: true - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - uglify-to-browserify@1.0.2: - resolution: {integrity: sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==} - - uglifyjs-webpack-plugin@0.4.6: - resolution: {integrity: sha512-TNM20HMW67kxHRNCZdvLyiwE1ST6WyY5Ae+TG55V81NpvNwJ9+V4/po4LHA1R9afV/WrqzfedG2UJCk2+swirw==} - engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} - peerDependencies: - webpack: ^1.9 || ^2 || ^2.1.0-beta || ^2.2.0-rc || ^3.0.0 - - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} - - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - unicode-properties@1.4.1: - resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} - - unicode-trie@2.0.0: - resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} - - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} engines: {node: '>=0.10.0'} - universal-cookie@8.1.2: - resolution: {integrity: sha512-kcKzTGNsxVytujrYOvQbvh//QyFrA53HrzCGyzh6i9ujCww5gfPrLK0tG+jJD40SIIldiEjBNPPSR8fBMS21GA==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - unset-value@1.0.0: - resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - unzipper@0.10.14: - resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} - - upath@1.2.0: - resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} - engines: {node: '>=4'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uqr@0.1.2: - resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - urix@0.1.0: - resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated - - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - url@0.11.4: - resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} - engines: {node: '>= 0.4'} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -10383,14 +10395,15 @@ packages: '@types/react': optional: true - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} + prisma@6.19.3: + resolution: {integrity: sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==} + engines: {node: '>=18.18'} + hasBin: true peerDependencies: '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - '@types/react': + typescript: optional: true use-sync-external-store@1.6.0: @@ -10411,171 +10424,124 @@ packages: util@0.11.1: resolution: {integrity: sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==} - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - utrie@1.0.2: - resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} - - uuid@11.1.1: - resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} - hasBin: true - - uuid@3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - 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). - hasBin: true - - uuid@8.3.2: - resolution: {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). - hasBin: true - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - validator@13.15.35: - resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} - vaul@1.1.2: - resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - vite-compatible-readable-stream@3.6.1: - resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@6.30.3: + resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: 18.3.1 + react-dom: 18.3.1 + + react-router@6.30.3: + resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: 18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - vm-browserify@1.1.2: - resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} - void-elements@3.1.0: - resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - w3c-keyname@2.2.8: - resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - warning-symbol@0.1.0: - resolution: {integrity: sha512-1S0lwbHo3kNUKA4VomBAhqn4DPjQkIKSdbOin5K7EFUQNwyIKx+wZMGXKI53RUjla8V2B8ouQduUlgtx8LoSMw==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - warning@4.0.3: - resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - wasm-feature-detect@1.8.0: - resolution: {integrity: sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} - watchpack-chokidar2@2.0.1: - resolution: {integrity: sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==} - - watchpack@1.7.5: - resolution: {integrity: sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==} - - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} - engines: {node: '>=10.13.0'} - - wbuf@1.7.3: - resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - web-encoding@1.1.5: - resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} @@ -10584,328 +10550,299 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - webpack-dev-middleware@1.12.2: - resolution: {integrity: sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==} - engines: {node: '>=0.6'} - peerDependencies: - webpack: ^1.0.0 || ^2.0.0 || ^3.0.0 - - webpack-dev-server@2.11.5: - resolution: {integrity: sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==} - engines: {node: '>=4.7'} - hasBin: true - peerDependencies: - webpack: ^2.2.0 || ^3.0.0 - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - webpack-node-externals@3.0.0: - resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} - engines: {node: '>=6'} - - webpack-sources@1.4.3: - resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} - - webpack-sources@3.4.1: - resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} - engines: {node: '>=10.13.0'} - - webpack@3.12.0: - resolution: {integrity: sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==} - engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} - hasBin: true - peerDependencies: - webpack-cli: '*' - webpack-command: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - webpack-command: - optional: true - - webpack@5.106.0: - resolution: {integrity: sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - websocket-driver@0.7.4: - resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} - engines: {node: '>=0.8.0'} - - websocket-extensions@0.1.4: - resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} - engines: {node: '>=0.8.0'} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-module@1.0.0: - resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==} - - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - - window-size@0.1.0: - resolution: {integrity: sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==} - engines: {node: '>= 0.8.0'} - - wmf@1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - word@0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - - wordwrap@0.0.2: - resolution: {integrity: sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==} - engines: {node: '>=0.4.0'} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@2.1.0: - resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==} - engines: {node: '>=0.10.0'} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xlsx@0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} hasBin: true - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - xml@1.0.1: - resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + rollup@4.60.3: + resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true - xmlhttprequest-ssl@2.1.2: - resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} - engines: {node: '>=0.4.0'} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} - y18n@3.2.2: - resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} - engines: {node: '>= 6'} + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - yargs-parser@4.2.1: - resolution: {integrity: sha512-+QQWqC2xeL0N5/TE+TY6OGEqyNRM+g2/r712PDNYgiCdXYCApXf1vzfmDSLBxfGRwV+moTq/V8FnMI24JCm2Yg==} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} - yargs-parser@7.0.0: - resolution: {integrity: sha512-WhzC+xgstid9MbVUktco/bf+KJG+Uu6vMX0LN1sLJvwmbCQVxb4D8LzogobonKycNasCZLdOzTAk1SK7+K7swg==} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@3.1.1: + resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yargs@3.10.0: - resolution: {integrity: sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==} - - yargs@6.6.0: - resolution: {integrity: sha512-6/QWTdisjnu5UHUzQGst+UOEuEVwIzFVGBjq3jMTFNs5WJQsH/X6nMURSaScIdF5txylr1Ao9bvbWiKi2yXbwA==} - - yargs@8.0.2: - resolution: {integrity: sha512-3RiZrpLpjrzIAKgGdPktBcMP/eG5bDFlkI+PHle1qwzyVXyDQL+pD/eZaMoOOO0Y7LLBfjpucObuUm/icvbpKQ==} - - year@0.2.1: - resolution: {integrity: sha512-9GnJUZ0QM4OgXuOzsKNzTJ5EOkums1Xc+3YQXp+Q+UxFjf7zLucp9dQ8QMIft0Szs1E1hUiXFim1OYfEKFq97w==} - engines: {node: '>=0.8'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} - - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} - yoga-layout@3.2.1: - resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} - zip-stream@4.1.1: - resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} - engines: {node: '>= 10'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - zlibjs@0.3.1: - resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + engines: {node: '>=14'} + + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} zustand@5.0.13: resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==} @@ -10925,42 +10862,25 @@ packages: use-sync-external-store: optional: true -snapshots: + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} - '@alloc/quick-lru@5.2.0': {} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} - '@angular-devkit/core@19.2.24(chokidar@4.0.3)': - dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - jsonc-parser: 3.3.1 - picomatch: 4.0.4 - rxjs: 7.8.1 - source-map: 0.7.4 - optionalDependencies: - chokidar: 4.0.3 + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} - '@angular-devkit/schematics-cli@19.2.24(@types/node@20.19.41)(chokidar@4.0.3)': - dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@20.19.41) - ansi-colors: 4.1.3 - symbol-observable: 4.0.0 - yargs-parser: 21.1.1 - transitivePeerDependencies: - - '@types/node' - - chokidar + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} - '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': - dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - jsonc-parser: 3.3.1 - magic-string: 0.30.17 - ora: 5.4.1 - rxjs: 7.8.1 - transitivePeerDependencies: - - chokidar + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} '@ark-ui/react@5.36.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -11034,6 +10954,839 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swagger-ui-dist@5.17.14: + resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + + swagger-ui-dist@5.32.4: + resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} + + swagger-ui-express@5.0.1: + resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} + engines: {node: '>= v0.10.32'} + peerDependencies: + express: '>=4.0.0 || >=5.0.0-beta' + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar 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 + + terser-webpack-plugin@5.6.0: + resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true + uglify-js: + optional: true + + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + '@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@ark-ui/react': 5.36.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@18.3.28)(react@19.2.6) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) + '@emotion/utils': 1.4.2 + '@pandacss/is-valid-prop': 1.11.1 + csstype: 3.2.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + turbo@2.9.12: + resolution: {integrity: sha512-lCPgus1NuTiBdaITWqzSH/Ff6HVL8HHGBtOXHg1dHRfcshN79XkygSdh0M6g8b0td91ILLG5MTkLOkp5UvyPJw==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typeorm@0.3.29: + resolution: {integrity: sha512-wwPEX/df4l72gCmOsrs0otJZYLGA9lLQkUZCkukbsymEycV4zXv2KM7wU7v2r8L01TaCgY9ApSSqHQWBOUhEoQ==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + '@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + transitivePeerDependencies: + - supports-color + + '@emotion/react@11.14.0(@types/react@18.3.28)(react@19.2.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.2.6 + optionalDependencies: + '@types/react': 18.3.28 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.2.3 + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + hasBin: true + + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@19.2.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@18.3.28)(react@19.2.6) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) + '@emotion/utils': 1.4.2 + react: 19.2.6 + optionalDependencies: + '@types/react': 18.3.28 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.6)': + dependencies: + react: 19.2.6 + + '@emotion/utils@1.4.2': {} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.4.1: + resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} + engines: {node: '>=10.13.0'} + + webpack@5.106.0: + resolution: {integrity: sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@5.5.0) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@floating-ui/react@0.27.19(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@floating-ui/utils': 0.2.11 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + tabbable: 6.4.0 + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + '@hookform/resolvers@5.2.2(react-hook-form@7.75.0(react@19.2.6))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.75.0(react@19.2.6) + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3(supports-color@5.5.0) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zustand@5.0.13: + resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: 18.3.1 + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@angular-devkit/core@19.2.24(chokidar@4.0.3)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + jsonc-parser: 3.3.1 + picomatch: 4.0.4 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics-cli@19.2.24(@types/node@20.19.41)(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) + '@inquirer/prompts': 7.3.2(@types/node@20.19.41) + ansi-colors: 4.1.3 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -11063,7 +11816,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@5.5.0) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -11206,840 +11959,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/runtime@7.29.2': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@bcoe/v8-coverage@0.2.3': {} - - '@borewit/text-codec@0.2.2': {} - - '@chakra-ui/react@3.35.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@ark-ui/react': 5.36.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@18.3.28)(react@19.2.6) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) - '@emotion/utils': 1.4.2 - '@pandacss/is-valid-prop': 1.11.1 - csstype: 3.2.3 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - - '@colors/colors@1.5.0': - optional: true - - '@commitlint/cli@19.8.1(@types/node@24.12.4)(typescript@5.9.3)': - dependencies: - '@commitlint/format': 19.8.1 - '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@24.12.4)(typescript@5.9.3) - '@commitlint/read': 19.8.1 - '@commitlint/types': 19.8.1 - tinyexec: 1.1.2 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - typescript - - '@commitlint/config-conventional@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - conventional-changelog-conventionalcommits: 7.0.2 - - '@commitlint/config-validator@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - ajv: 8.20.0 - - '@commitlint/ensure@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - lodash.startcase: 4.4.0 - lodash.upperfirst: 4.3.1 - - '@commitlint/execute-rule@19.8.1': {} - - '@commitlint/format@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - chalk: 5.6.2 - - '@commitlint/is-ignored@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - semver: 7.8.0 - - '@commitlint/lint@19.8.1': - dependencies: - '@commitlint/is-ignored': 19.8.1 - '@commitlint/parse': 19.8.1 - '@commitlint/rules': 19.8.1 - '@commitlint/types': 19.8.1 - - '@commitlint/load@19.8.1(@types/node@24.12.4)(typescript@5.9.3)': - dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/execute-rule': 19.8.1 - '@commitlint/resolve-extends': 19.8.1 - '@commitlint/types': 19.8.1 - chalk: 5.6.2 - cosmiconfig: 9.0.1(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@24.12.4)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 - transitivePeerDependencies: - - '@types/node' - - typescript - - '@commitlint/message@19.8.1': {} - - '@commitlint/parse@19.8.1': - dependencies: - '@commitlint/types': 19.8.1 - conventional-changelog-angular: 7.0.0 - conventional-commits-parser: 5.0.0 - - '@commitlint/read@19.8.1': - dependencies: - '@commitlint/top-level': 19.8.1 - '@commitlint/types': 19.8.1 - git-raw-commits: 4.0.0 - minimist: 1.2.8 - tinyexec: 1.1.2 - - '@commitlint/resolve-extends@19.8.1': - dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/types': 19.8.1 - global-directory: 4.0.1 - import-meta-resolve: 4.2.0 - lodash.mergewith: 4.6.2 - resolve-from: 5.0.0 - - '@commitlint/rules@19.8.1': - dependencies: - '@commitlint/ensure': 19.8.1 - '@commitlint/message': 19.8.1 - '@commitlint/to-lines': 19.8.1 - '@commitlint/types': 19.8.1 - - '@commitlint/to-lines@19.8.1': {} - - '@commitlint/top-level@19.8.1': - dependencies: - find-up: 7.0.0 - - '@commitlint/types@19.8.1': - dependencies: - '@types/conventional-commits-parser': 5.0.2 - chalk: 5.6.2 - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@csstools/color-helpers@5.1.0': {} - - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@date-fns/tz@1.4.1': {} - - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.28.6 - '@babel/runtime': 7.29.2 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/hash@0.9.2': {} - - '@emotion/is-prop-valid@1.4.0': - dependencies: - '@emotion/memoize': 0.9.0 - - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - transitivePeerDependencies: - - supports-color - - '@emotion/react@11.14.0(@types/react@18.3.28)(react@19.2.6)': - dependencies: - '@babel/runtime': 7.29.2 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.2.6 - optionalDependencies: - '@types/react': 18.3.28 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.2.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) - '@emotion/utils': 1.4.2 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 - transitivePeerDependencies: - - supports-color - - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@19.2.6)': - dependencies: - '@babel/runtime': 7.29.2 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@18.3.28)(react@19.2.6) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) - '@emotion/utils': 1.4.2 - react: 19.2.6 - optionalDependencies: - '@types/react': 18.3.28 - transitivePeerDependencies: - - supports-color - - '@emotion/unitless@0.10.0': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': - dependencies: - react: 18.3.1 - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.6)': - dependencies: - react: 19.2.6 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.15.0 - debug: 4.4.3(supports-color@5.5.0) - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@faker-js/faker@10.4.0': {} - - '@fast-csv/format@4.3.5': - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.isboolean: 3.0.3 - lodash.isequal: 4.5.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - - '@fast-csv/parse@4.3.6': - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.groupby: 4.6.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - lodash.isundefined: 3.0.1 - lodash.uniq: 4.5.0 - - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - - '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - - '@floating-ui/react@0.27.19(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@floating-ui/utils': 0.2.11 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - tabbable: 6.4.0 - - '@floating-ui/utils@0.2.11': {} - - '@gilbarbara/deep-equal@0.1.2': {} - - '@gilbarbara/deep-equal@0.3.1': {} - - '@hookform/resolvers@5.2.2(react-hook-form@7.75.0(react@19.2.6))': - dependencies: - '@standard-schema/utils': 0.3.0 - react-hook-form: 7.75.0(react@19.2.6) - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3(supports-color@5.5.0) - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@inquirer/ansi@1.0.2': {} - - '@inquirer/checkbox@4.3.2(@types/node@20.19.41)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@20.19.41) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/confirm@5.1.21(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/core@10.3.2(@types/node@20.19.41)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@20.19.41) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/editor@4.2.23(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/external-editor': 1.0.3(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/expand@4.0.23(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/external-editor@1.0.3(@types/node@20.19.41)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/figures@1.0.15': {} - - '@inquirer/input@4.3.1(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/number@3.0.23(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/password@4.0.23(@types/node@20.19.41)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/prompts@7.10.1(@types/node@20.19.41)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@20.19.41) - '@inquirer/confirm': 5.1.21(@types/node@20.19.41) - '@inquirer/editor': 4.2.23(@types/node@20.19.41) - '@inquirer/expand': 4.0.23(@types/node@20.19.41) - '@inquirer/input': 4.3.1(@types/node@20.19.41) - '@inquirer/number': 3.0.23(@types/node@20.19.41) - '@inquirer/password': 4.0.23(@types/node@20.19.41) - '@inquirer/rawlist': 4.1.11(@types/node@20.19.41) - '@inquirer/search': 3.2.2(@types/node@20.19.41) - '@inquirer/select': 4.4.2(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/prompts@7.3.2(@types/node@20.19.41)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@20.19.41) - '@inquirer/confirm': 5.1.21(@types/node@20.19.41) - '@inquirer/editor': 4.2.23(@types/node@20.19.41) - '@inquirer/expand': 4.0.23(@types/node@20.19.41) - '@inquirer/input': 4.3.1(@types/node@20.19.41) - '@inquirer/number': 3.0.23(@types/node@20.19.41) - '@inquirer/password': 4.0.23(@types/node@20.19.41) - '@inquirer/rawlist': 4.1.11(@types/node@20.19.41) - '@inquirer/search': 3.2.2(@types/node@20.19.41) - '@inquirer/select': 4.4.2(@types/node@20.19.41) - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/rawlist@4.1.11(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/type': 3.0.10(@types/node@20.19.41) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/search@3.2.2(@types/node@20.19.41)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@20.19.41) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/select@4.4.2(@types/node@20.19.41)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@20.19.41) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@20.19.41) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.41 - - '@inquirer/type@3.0.10(@types/node@20.19.41)': - optionalDependencies: - '@types/node': 20.19.41 - - '@internationalized/date@3.12.0': - dependencies: - '@swc/helpers': 0.5.21 - - '@internationalized/number@3.6.5': - dependencies: - '@swc/helpers': 0.5.21 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.2 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.6': {} - - '@jest/console@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.41 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.41 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.41)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.41 - jest-mock: 29.7.0 - - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 - - '@jest/expect@29.7.0': - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.41 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - '@jest/globals@29.7.0': - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/reporters@29.7.0': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 20.19.41 - chalk: 4.1.2 - collect-v8-coverage: 1.0.3 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.2.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.10 - - '@jest/source-map@29.6.3': - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - '@jest/test-result@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.3 - - '@jest/test-sequencer@29.7.0': - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 - - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.29.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - '@jest/types@29.6.3': - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.41 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@leichtgewicht/ip-codec@2.0.5': {} - '@lottiefiles/react-lottie-player@3.6.0(react@19.2.6)': dependencies: lottie-web: 5.13.0 @@ -12074,48 +11993,26 @@ snapshots: dependencies: react: 19.2.6 - '@mapbox/node-pre-gyp@1.0.11': + '@babel/traverse@7.29.0': dependencies: - detect-libc: 2.1.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.8.0 - tar: 6.2.1 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 transitivePeerDependencies: - - encoding - supports-color - '@microsoft/tsdoc@0.15.1': {} - - '@microsoft/tsdoc@0.16.0': {} - - '@mui/base@5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@babel/types@7.29.0': dependencies: - '@babel/runtime': 7.29.2 - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.28) - '@mui/utils': 6.4.9(@types/react@18.3.28)(react@18.3.1) - '@popperjs/core': 2.11.8 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@mui/core-downloads-tracker@5.18.0': {} + '@bcoe/v8-coverage@0.2.3': {} - '@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 + '@borewit/text-codec@0.2.2': {} '@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -12138,16 +12035,20 @@ snapshots: '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@19.2.6) '@types/react': 18.3.28 - '@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)': + '@commitlint/cli@19.8.1(@types/node@24.12.4)(typescript@5.9.3)': dependencies: - '@babel/runtime': 7.29.2 - '@mui/utils': 5.17.1(@types/react@18.3.28)(react@18.3.1) - prop-types: 15.8.1 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.28 + '@commitlint/format': 19.8.1 + '@commitlint/lint': 19.8.1 + '@commitlint/load': 19.8.1(@types/node@24.12.4)(typescript@5.9.3) + '@commitlint/read': 19.8.1 + '@commitlint/types': 19.8.1 + tinyexec: 1.1.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript - '@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(react@18.3.1)': + '@commitlint/config-conventional@19.8.1': dependencies: '@babel/runtime': 7.29.2 '@emotion/cache': 11.14.0 @@ -12159,7 +12060,7 @@ snapshots: '@emotion/react': 11.14.0(@types/react@18.3.28)(react@19.2.6) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@19.2.6) - '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)': + '@commitlint/config-validator@19.8.1': dependencies: '@babel/runtime': 7.29.2 '@mui/private-theming': 5.17.1(@types/react@18.3.28)(react@18.3.1) @@ -12179,29 +12080,21 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 - '@mui/utils@5.17.1(@types/react@18.3.28)(react@18.3.1)': + '@commitlint/ensure@19.8.1': dependencies: - '@babel/runtime': 7.29.2 - '@mui/types': 7.2.24(@types/react@18.3.28) - '@types/prop-types': 15.7.15 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.3.1 - react-is: 19.2.6 - optionalDependencies: - '@types/react': 18.3.28 + '@commitlint/types': 19.8.1 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 - '@mui/utils@6.4.9(@types/react@18.3.28)(react@18.3.1)': + '@commitlint/execute-rule@19.8.1': {} + + '@commitlint/format@19.8.1': dependencies: - '@babel/runtime': 7.29.2 - '@mui/types': 7.2.24(@types/react@18.3.28) - '@types/prop-types': 15.7.15 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.3.1 - react-is: 19.2.6 - optionalDependencies: - '@types/react': 18.3.28 + '@commitlint/types': 19.8.1 + chalk: 5.6.2 '@mui/x-date-pickers@6.20.2(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(date-fns@4.1.0)(dayjs@1.11.20)(luxon@3.7.2)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -12298,240 +12191,132 @@ snapshots: '@nestjs/axios@4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.16.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - axios: 1.16.0 - rxjs: 7.8.2 + '@commitlint/is-ignored': 19.8.1 + '@commitlint/parse': 19.8.1 + '@commitlint/rules': 19.8.1 + '@commitlint/types': 19.8.1 - '@nestjs/cli@11.0.21(@types/node@20.19.41)(prettier@3.8.3)': + '@commitlint/load@19.8.1(@types/node@24.12.4)(typescript@5.9.3)': dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.24(@types/node@20.19.41)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@20.19.41) - '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) - ansis: 4.2.0 - chokidar: 4.0.3 - cli-table3: 0.6.5 - commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0) - glob: 13.0.6 - node-emoji: 1.11.0 - ora: 5.4.1 - tsconfig-paths: 4.2.0 - tsconfig-paths-webpack-plugin: 4.2.0 - typescript: 5.9.3 - webpack: 5.106.0 - webpack-node-externals: 3.0.0 + '@commitlint/config-validator': 19.8.1 + '@commitlint/execute-rule': 19.8.1 + '@commitlint/resolve-extends': 19.8.1 + '@commitlint/types': 19.8.1 + chalk: 5.6.2 + cosmiconfig: 9.0.1(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@24.12.4)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 transitivePeerDependencies: - - '@minify-html/node' - - '@swc/css' - - '@swc/html' - '@types/node' - - clean-css - - cssnano - - csso - - esbuild - - html-minifier-terser - - lightningcss - - postcss - - prettier - - uglify-js - - webpack-cli + - typescript - '@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@commitlint/message@19.8.1': {} + + '@commitlint/parse@19.8.1': dependencies: - file-type: 21.3.4 - iterare: 1.2.1 - load-esm: 1.0.3 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.4 - transitivePeerDependencies: - - supports-color + '@commitlint/types': 19.8.1 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 - '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + '@commitlint/read@19.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - dotenv: 17.4.1 - dotenv-expand: 12.0.3 - lodash: 4.18.1 - rxjs: 7.8.2 + '@commitlint/top-level': 19.8.1 + '@commitlint/types': 19.8.1 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + tinyexec: 1.1.2 - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@commitlint/resolve-extends@19.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nuxt/opencollective': 0.4.1 - fast-safe-stringify: 2.1.1 - iterare: 1.2.1 - path-to-regexp: 8.4.2 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - optionalDependencies: - '@nestjs/microservices': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@commitlint/config-validator': 19.8.1 + '@commitlint/types': 19.8.1 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 - '@nestjs/event-emitter@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@commitlint/rules@19.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - eventemitter2: 6.4.9 + '@commitlint/ensure': 19.8.1 + '@commitlint/message': 19.8.1 + '@commitlint/to-lines': 19.8.1 + '@commitlint/types': 19.8.1 - '@nestjs/jwt@10.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + '@commitlint/to-lines@19.8.1': {} + + '@commitlint/top-level@19.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@types/jsonwebtoken': 9.0.5 - jsonwebtoken: 9.0.2 + find-up: 7.0.0 - '@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + '@commitlint/types@19.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@types/jsonwebtoken': 9.0.10 - jsonwebtoken: 9.0.3 + '@types/conventional-commits-parser': 5.0.2 + chalk: 5.6.2 - '@nestjs/mapped-types@2.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@cspotcode/source-map-support@0.8.1': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - reflect-metadata: 0.2.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.4 + '@jridgewell/trace-mapping': 0.3.9 - '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - reflect-metadata: 0.2.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.4 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@nestjs/microservices@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - iterare: 1.2.1 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@nestjs/passport@10.0.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - passport: 0.7.0 + '@csstools/css-tokenizer': 3.0.4 - '@nestjs/passport@11.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - passport: 0.7.0 + '@csstools/css-tokenizer@3.0.4': {} - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cors: 2.8.6 - express: 5.2.1 - multer: 2.1.1 - path-to-regexp: 8.4.2 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color + '@esbuild/aix-ppc64@0.21.5': + optional: true - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cron: 4.4.0 + '@esbuild/android-arm64@0.21.5': + optional: true - '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': - dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - comment-json: 5.0.0 - jsonc-parser: 3.3.1 - pluralize: 8.0.0 - typescript: 5.9.3 - optionalDependencies: - prettier: 3.8.3 - transitivePeerDependencies: - - chokidar + '@esbuild/android-arm@0.21.5': + optional: true - '@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': - dependencies: - '@microsoft/tsdoc': 0.16.0 - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) - js-yaml: 4.1.1 - lodash: 4.18.1 - path-to-regexp: 8.4.2 - reflect-metadata: 0.2.2 - swagger-ui-dist: 5.32.4 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.4 + '@esbuild/android-x64@0.21.5': + optional: true - '@nestjs/swagger@7.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': - dependencies: - '@microsoft/tsdoc': 0.15.1 - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) - js-yaml: 4.1.0 - lodash: 4.17.21 - path-to-regexp: 3.3.0 - reflect-metadata: 0.2.2 - swagger-ui-dist: 5.17.14 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.4 + '@esbuild/darwin-arm64@0.21.5': + optional: true - '@nestjs/testing@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - tslib: 2.8.1 - optionalDependencies: - '@nestjs/microservices': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@esbuild/darwin-x64@0.21.5': + optional: true - '@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - reflect-metadata: 0.2.2 + '@esbuild/freebsd-arm64@0.21.5': + optional: true - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)))': - dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - typeorm: 0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) + '@esbuild/freebsd-x64@0.21.5': + optional: true - '@noble/ciphers@1.3.0': {} + '@esbuild/linux-arm64@0.21.5': + optional: true - '@noble/hashes@1.8.0': {} + '@esbuild/linux-arm@0.21.5': + optional: true - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@esbuild/linux-ia32@0.21.5': + optional: true - '@nodelib/fs.stat@2.0.5': {} + '@esbuild/linux-loong64@0.21.5': + optional: true - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@nuxt/opencollective@0.4.1': - dependencies: - consola: 3.4.2 + '@esbuild/linux-mips64el@0.21.5': + optional: true '@onlyoffice/document-editor-react@2.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12539,47 +12324,67 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) - '@pandacss/is-valid-prop@1.11.1': {} - - '@paralleldrive/cuid2@2.3.1': - dependencies: - '@noble/hashes': 1.8.0 - - '@phc/format@1.0.0': {} - - '@pkgjs/parseargs@0.11.0': + '@esbuild/linux-riscv64@0.21.5': optional: true - '@popperjs/core@2.11.8': {} + '@esbuild/linux-s390x@0.21.5': + optional: true - '@prisma/client@5.22.0(prisma@5.22.0)': - optionalDependencies: - prisma: 5.22.0 + '@esbuild/linux-x64@0.21.5': + optional: true - '@prisma/debug@5.22.0': {} + '@esbuild/netbsd-x64@0.21.5': + optional: true - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} + '@esbuild/openbsd-x64@0.21.5': + optional: true - '@prisma/engines@5.22.0': + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/fetch-engine': 5.22.0 - '@prisma/get-platform': 5.22.0 + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 - '@prisma/fetch-engine@5.22.0': + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/get-platform': 5.22.0 + ajv: 6.15.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color - '@prisma/get-platform@5.22.0': + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': dependencies: - '@prisma/debug': 5.22.0 + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color - '@radix-ui/number@1.1.1': {} + '@humanwhocodes/module-importer@1.0.1': {} - '@radix-ui/primitive@1.1.3': {} + '@humanwhocodes/object-schema@2.0.3': {} '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12609,8 +12414,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12618,8 +12422,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-avatar@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12663,8 +12466,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12675,14 +12477,13 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@19.2.6)': dependencies: react: 19.2.6 optionalDependencies: - '@types/react': 18.3.28 + '@types/node': 20.19.41 '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12695,8 +12496,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@19.2.6)': dependencies: @@ -12729,14 +12529,13 @@ snapshots: react-dom: 19.2.6(react@19.2.6) react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@19.2.6)': dependencies: react: 19.2.6 optionalDependencies: - '@types/react': 18.3.28 + '@types/node': 20.19.41 '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12748,8 +12547,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12763,14 +12561,13 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@19.2.6)': dependencies: react: 19.2.6 optionalDependencies: - '@types/react': 18.3.28 + '@types/node': 20.19.41 '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12797,15 +12594,14 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@19.2.6)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.6) react: 19.2.6 optionalDependencies: - '@types/react': 18.3.28 + '@types/node': 20.19.41 '@radix-ui/react-label@2.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -12902,8 +12698,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/node': 20.19.41 '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13243,7 +13038,7 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) - '@radix-ui/rect@1.1.1': {} + '@lukeed/csprng@1.1.0': {} '@react-pdf-viewer/attachment@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13251,7 +13046,8 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - - pdfjs-dist + - encoding + - supports-color '@react-pdf-viewer/bookmark@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13277,7 +13073,20 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - - pdfjs-dist + - '@minify-html/node' + - '@swc/css' + - '@swc/html' + - '@types/node' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - prettier + - uglify-js + - webpack-cli '@react-pdf-viewer/full-screen@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13285,7 +13094,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - - pdfjs-dist + - supports-color '@react-pdf-viewer/get-file@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13349,7 +13158,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - - pdfjs-dist + - supports-color '@react-pdf-viewer/selection-mode@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13365,7 +13174,7 @@ snapshots: react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - - pdfjs-dist + - chokidar '@react-pdf-viewer/thumbnail@3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -13403,47 +13212,30 @@ snapshots: transitivePeerDependencies: - pdfjs-dist - '@react-pdf/fns@3.1.3': {} + '@noble/hashes@1.8.0': {} - '@react-pdf/font@4.0.8': + '@nodelib/fs.scandir@2.1.5': dependencies: - '@react-pdf/pdfkit': 5.1.1 - '@react-pdf/types': 2.11.1 - fontkit: 2.0.4 - is-url: 1.2.4 + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 - '@react-pdf/image@3.1.0': - dependencies: - '@react-pdf/svg': 1.1.0 - jay-peg: 1.1.1 - png-js: 2.0.0 + '@nodelib/fs.stat@2.0.5': {} - '@react-pdf/layout@4.6.1': + '@nodelib/fs.walk@1.2.8': dependencies: - '@react-pdf/fns': 3.1.3 - '@react-pdf/image': 3.1.0 - '@react-pdf/primitives': 4.3.0 - '@react-pdf/stylesheet': 6.2.1 - '@react-pdf/textkit': 6.3.0 - '@react-pdf/types': 2.11.1 - emoji-regex-xs: 1.0.0 - queue: 6.0.2 - yoga-layout: 3.2.1 + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 - '@react-pdf/pdfkit@5.1.1': + '@nuxt/opencollective@0.4.1': + dependencies: + consola: 3.4.2 + + '@paralleldrive/cuid2@2.3.1': dependencies: - '@babel/runtime': 7.29.2 - '@noble/ciphers': 1.3.0 '@noble/hashes': 1.8.0 - browserify-zlib: 0.2.0 - fontkit: 2.0.4 - jay-peg: 1.1.1 - js-md5: 0.8.3 - linebreak: 1.1.0 - png-js: 2.0.0 - vite-compatible-readable-stream: 3.6.1 - '@react-pdf/primitives@4.3.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true '@react-pdf/reconciler@2.0.0(react@19.2.6)': dependencies: @@ -13481,31 +13273,22 @@ snapshots: queue: 6.0.2 react: 19.2.6 - '@react-pdf/stylesheet@6.2.1': - dependencies: - '@react-pdf/fns': 3.1.3 - '@react-pdf/types': 2.11.1 - color-string: 2.1.4 - hsl-to-hex: 1.0.0 - media-engine: 1.0.3 - postcss-value-parser: 4.2.0 + '@prisma/debug@6.19.3': {} - '@react-pdf/svg@1.1.0': - dependencies: - '@react-pdf/primitives': 4.3.0 + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} - '@react-pdf/textkit@6.3.0': + '@prisma/engines@6.19.3': dependencies: - '@react-pdf/fns': 3.1.3 - bidi-js: 1.0.3 - hyphen: 1.14.1 - unicode-properties: 1.4.1 + '@prisma/debug': 6.19.3 + '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/fetch-engine': 6.19.3 + '@prisma/get-platform': 6.19.3 - '@react-pdf/types@2.11.1': + '@prisma/fetch-engine@6.19.3': dependencies: - '@react-pdf/font': 4.0.8 - '@react-pdf/primitives': 4.3.0 - '@react-pdf/stylesheet': 6.2.1 + '@prisma/debug': 6.19.3 + '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/get-platform': 6.19.3 '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@19.2.6)(redux@5.0.1))(react@19.2.6)': dependencies: @@ -13630,8 +13413,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@socket.io/component-emitter@3.1.2': {} - '@sqltools/formatter@1.2.5': {} '@standard-schema/spec@1.1.0': {} @@ -14376,30 +14157,6 @@ snapshots: '@types/cookiejar@2.1.5': {} - '@types/d3-array@3.2.2': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.1': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -14431,11 +14188,6 @@ snapshots: dependencies: '@types/node': 20.19.41 - '@types/hoist-non-react-statics@3.3.7(@types/react@18.3.28)': - dependencies: - '@types/react': 18.3.28 - hoist-non-react-statics: 3.3.2 - '@types/http-errors@2.0.5': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -14453,8 +14205,6 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 - '@types/jquery@4.0.0': {} - '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -14474,8 +14224,6 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@14.18.63': {} - '@types/node@20.19.41': dependencies: undici-types: 6.21.0 @@ -14484,9 +14232,8 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/pako@2.0.4': {} - - '@types/parse-json@4.0.2': {} + '@types/parse-json@4.0.2': + optional: true '@types/passport-jwt@4.0.1': dependencies: @@ -14510,19 +14257,12 @@ snapshots: '@types/qs@6.15.1': {} - '@types/raf@3.4.3': - optional: true - '@types/range-parser@1.2.7': {} '@types/react-dom@18.3.7(@types/react@18.3.28)': dependencies: '@types/react': 18.3.28 - '@types/react-transition-group@4.4.12(@types/react@18.3.28)': - dependencies: - '@types/react': 18.3.28 - '@types/react@18.3.28': dependencies: '@types/prop-types': 15.7.15 @@ -14537,8 +14277,6 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 20.19.41 - '@types/signature_pad@2.3.6': {} - '@types/stack-utils@2.0.3': {} '@types/superagent@8.1.9': @@ -14553,15 +14291,6 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 - '@types/tinymce@4.6.9': - dependencies: - '@types/jquery': 4.0.0 - - '@types/trusted-types@2.0.7': - optional: true - - '@types/use-sync-external-store@0.0.6': {} - '@types/validator@13.15.10': {} '@types/yargs-parser@21.0.3': {} @@ -14716,411 +14445,6 @@ snapshots: tinyrainbow: 1.2.0 '@webassemblyjs/ast@1.14.1': - dependencies: - '@webassemblyjs/helper-numbers': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - - '@webassemblyjs/helper-api-error@1.13.2': {} - - '@webassemblyjs/helper-buffer@1.14.1': {} - - '@webassemblyjs/helper-numbers@1.13.2': - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.13.2 - '@webassemblyjs/helper-api-error': 1.13.2 - '@xtuc/long': 4.2.2 - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - - '@webassemblyjs/helper-wasm-section@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/wasm-gen': 1.14.1 - - '@webassemblyjs/ieee754@1.13.2': - dependencies: - '@xtuc/ieee754': 1.2.0 - - '@webassemblyjs/leb128@1.13.2': - dependencies: - '@xtuc/long': 4.2.2 - - '@webassemblyjs/utf8@1.13.2': {} - - '@webassemblyjs/wasm-edit@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/helper-wasm-section': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-opt': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wast-printer': 1.14.1 - - '@webassemblyjs/wasm-gen@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wasm-opt@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - - '@webassemblyjs/wasm-parser@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-api-error': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wast-printer@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@xtuc/long': 4.2.2 - - '@xtuc/ieee754@1.2.0': {} - - '@xtuc/long@4.2.2': {} - - '@zag-js/accordion@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/anatomy@1.40.0': {} - - '@zag-js/angle-slider@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/rect-utils': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/aria-hidden@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - - '@zag-js/async-list@1.40.0': - dependencies: - '@zag-js/core': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/auto-resize@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - - '@zag-js/avatar@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/carousel@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/scroll-snap': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/cascade-select@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/collection': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/rect-utils': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/checkbox@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/clipboard@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/collapsible@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/collection@1.40.0': - dependencies: - '@zag-js/utils': 1.40.0 - - '@zag-js/color-picker@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/color-utils': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/color-utils@1.40.0': - dependencies: - '@zag-js/utils': 1.40.0 - - '@zag-js/combobox@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/collection': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/live-region': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/core@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/date-input@1.40.0(@internationalized/date@3.12.0)': - dependencies: - '@internationalized/date': 3.12.0 - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/date-utils': 1.40.0(@internationalized/date@3.12.0) - '@zag-js/dom-query': 1.40.0 - '@zag-js/live-region': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/date-picker@1.40.0(@internationalized/date@3.12.0)': - dependencies: - '@internationalized/date': 3.12.0 - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/date-utils': 1.40.0(@internationalized/date@3.12.0) - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/live-region': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/date-utils@1.40.0(@internationalized/date@3.12.0)': - dependencies: - '@internationalized/date': 3.12.0 - - '@zag-js/dialog@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/aria-hidden': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-trap': 1.40.0 - '@zag-js/remove-scroll': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/dismissable@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - '@zag-js/interact-outside': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/dom-query@1.40.0': - dependencies: - '@zag-js/types': 1.40.0 - - '@zag-js/drawer@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/aria-hidden': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-trap': 1.40.0 - '@zag-js/remove-scroll': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/editable@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/interact-outside': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/file-upload@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/file-utils': 1.40.0 - '@zag-js/i18n-utils': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/file-utils@1.40.0': - dependencies: - '@zag-js/i18n-utils': 1.40.0 - - '@zag-js/floating-panel@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/rect-utils': 1.40.0 - '@zag-js/store': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/focus-trap@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - - '@zag-js/focus-visible@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - - '@zag-js/highlight-word@1.40.0': {} - - '@zag-js/hover-card@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/i18n-utils@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - - '@zag-js/image-cropper@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/interact-outside@1.40.0': - dependencies: - '@zag-js/dom-query': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/json-tree-utils@1.40.0': {} - - '@zag-js/listbox@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/collection': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/live-region@1.40.0': {} - - '@zag-js/marquee@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/menu@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/rect-utils': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/navigation-menu@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/number-input@1.40.0': - dependencies: - '@internationalized/number': 3.6.5 - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/pagination@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/password-input@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/pin-input@1.40.0': dependencies: '@zag-js/anatomy': 1.40.0 '@zag-js/core': 1.40.0 @@ -15199,171 +14523,80 @@ snapshots: '@zag-js/rect-utils@1.40.0': {} - '@zag-js/remove-scroll@1.40.0': + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': dependencies: - '@zag-js/dom-query': 1.40.0 + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 - '@zag-js/scroll-area@1.40.0': + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 - '@zag-js/scroll-snap@1.40.0': + '@webassemblyjs/ieee754@1.13.2': dependencies: - '@zag-js/dom-query': 1.40.0 + '@xtuc/ieee754': 1.2.0 - '@zag-js/select@1.40.0': + '@webassemblyjs/leb128@1.13.2': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/collection': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@xtuc/long': 4.2.2 - '@zag-js/signature-pad@1.40.0': + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - perfect-freehand: 1.2.3 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 - '@zag-js/slider@1.40.0': + '@webassemblyjs/wasm-gen@1.14.1': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@zag-js/splitter@1.40.0': + '@webassemblyjs/wasm-opt@1.14.1': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 - '@zag-js/steps@1.40.0': + '@webassemblyjs/wasm-parser@1.14.1': dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@zag-js/store@1.40.0': + '@webassemblyjs/wast-printer@1.14.1': dependencies: - proxy-compare: 3.0.1 + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 - '@zag-js/switch@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 + '@xtuc/ieee754@1.2.0': {} - '@zag-js/tabs@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/tags-input@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/auto-resize': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/interact-outside': 1.40.0 - '@zag-js/live-region': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/timer@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/toast@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/toggle-group@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/toggle@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/tooltip@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-visible': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/tour@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dismissable': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/focus-trap': 1.40.0 - '@zag-js/interact-outside': 1.40.0 - '@zag-js/popper': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/tree-view@1.40.0': - dependencies: - '@zag-js/anatomy': 1.40.0 - '@zag-js/collection': 1.40.0 - '@zag-js/core': 1.40.0 - '@zag-js/dom-query': 1.40.0 - '@zag-js/types': 1.40.0 - '@zag-js/utils': 1.40.0 - - '@zag-js/types@1.40.0': - dependencies: - csstype: 3.2.3 - - '@zag-js/utils@1.40.0': {} - - '@zxing/text-encoding@0.9.0': - optional: true + '@xtuc/long@4.2.2': {} JSONStream@1.3.5: dependencies: @@ -15372,26 +14605,11 @@ snapshots: abbrev@1.1.1: {} - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - - abs-svg-path@0.1.1: {} - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - accepts@2.0.0: dependencies: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-dynamic-import@2.0.2: - dependencies: - acorn: 4.0.13 - acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -15404,14 +14622,8 @@ snapshots: dependencies: acorn: 8.16.0 - acorn@4.0.13: {} - - acorn@5.7.4: {} - acorn@8.16.0: {} - adler-32@1.3.1: {} - agent-base@6.0.2: dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -15458,96 +14670,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - align-text@0.1.4: - dependencies: - kind-of: 3.2.2 - longest: 1.0.1 - repeat-string: 1.6.1 - - ansi-bgblack@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgblue@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgcyan@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bggreen@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgmagenta@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgred@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgwhite@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bgyellow@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-black@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-blue@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-bold@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-colors@0.2.0: - dependencies: - ansi-bgblack: 0.1.1 - ansi-bgblue: 0.1.1 - ansi-bgcyan: 0.1.1 - ansi-bggreen: 0.1.1 - ansi-bgmagenta: 0.1.1 - ansi-bgred: 0.1.1 - ansi-bgwhite: 0.1.1 - ansi-bgyellow: 0.1.1 - ansi-black: 0.1.1 - ansi-blue: 0.1.1 - ansi-bold: 0.1.1 - ansi-cyan: 0.1.1 - ansi-dim: 0.1.1 - ansi-gray: 0.1.1 - ansi-green: 0.1.1 - ansi-grey: 0.1.1 - ansi-hidden: 0.1.1 - ansi-inverse: 0.1.1 - ansi-italic: 0.1.1 - ansi-magenta: 0.1.1 - ansi-red: 0.1.1 - ansi-reset: 0.1.1 - ansi-strikethrough: 0.1.1 - ansi-underline: 0.1.1 - ansi-white: 0.1.1 - ansi-yellow: 0.1.1 - lazy-cache: 2.0.2 - ansi-colors@4.1.3: {} - ansi-cyan@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-dim@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -15556,56 +14680,10 @@ snapshots: dependencies: environment: 1.1.0 - ansi-gray@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-green@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-grey@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-hidden@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-html@0.0.7: {} - - ansi-inverse@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-italic@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-magenta@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-red@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-regex@2.1.1: {} - - ansi-regex@3.0.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} - ansi-reset@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-strikethrough@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -15614,41 +14692,12 @@ snapshots: ansi-styles@6.2.3: {} - ansi-underline@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-white@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - - ansi-wrap@0.1.0: {} - - ansi-yellow@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - ansis@4.2.0: {} ansis@4.3.0: {} any-promise@1.3.0: {} - anymatch@2.0.0(supports-color@4.5.0): - dependencies: - micromatch: 3.1.10(supports-color@4.5.0) - normalize-path: 2.1.1 - transitivePeerDependencies: - - supports-color - optional: true - - anymatch@2.0.0(supports-color@5.5.0): - dependencies: - micromatch: 3.1.10(supports-color@5.5.0) - normalize-path: 2.1.1 - transitivePeerDependencies: - - supports-color - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -15660,42 +14709,6 @@ snapshots: aproba@2.1.0: {} - archiver-utils@2.1.0: - dependencies: - glob: 7.2.3 - graceful-fs: 4.2.11 - lazystream: 1.0.1 - lodash.defaults: 4.2.0 - lodash.difference: 4.5.0 - lodash.flatten: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.union: 4.6.0 - normalize-path: 3.0.0 - readable-stream: 2.3.8 - - archiver-utils@3.0.4: - dependencies: - glob: 7.2.3 - graceful-fs: 4.2.11 - lazystream: 1.0.1 - lodash.defaults: 4.2.0 - lodash.difference: 4.5.0 - lodash.flatten: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.union: 4.6.0 - normalize-path: 3.0.0 - readable-stream: 3.6.2 - - archiver@5.3.2: - dependencies: - archiver-utils: 2.1.0 - async: 3.2.6 - buffer-crc32: 0.2.13 - readable-stream: 3.6.2 - readdir-glob: 1.1.3 - tar-stream: 2.2.0 - zip-stream: 4.1.1 - are-we-there-yet@2.0.0: dependencies: delegates: 1.0.0 @@ -15705,39 +14718,17 @@ snapshots: arg@5.0.2: {} - argon2@0.43.1: - dependencies: - '@phc/format': 1.0.0 - node-addon-api: 8.7.0 - node-gyp-build: 4.8.4 - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 argparse@2.0.1: {} - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - - arr-diff@4.0.0: {} - - arr-flatten@1.1.0: {} - - arr-union@3.1.0: {} - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-find-index@1.0.2: {} - - array-flatten@1.1.1: {} - - array-flatten@2.1.2: {} - array-ify@1.0.0: {} array-includes@3.1.9: @@ -15751,22 +14742,8 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 - array-sort@0.1.4: - dependencies: - default-compare: 1.0.0 - get-value: 2.0.6 - kind-of: 5.1.0 - array-timsort@1.0.3: {} - array-union@1.0.2: - dependencies: - array-uniq: 1.0.3 - - array-uniq@1.0.3: {} - - array-unique@0.3.2: {} - array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.9 @@ -15820,47 +14797,12 @@ snapshots: asap@2.0.6: {} - asn1.js@4.10.1: - dependencies: - bn.js: 4.12.3 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - - assert@1.5.1: - dependencies: - object.assign: 4.1.7 - util: 0.10.4 - assertion-error@2.0.1: {} - assign-symbols@1.0.0: {} - - async-each@1.0.6: {} - async-function@1.0.0: {} - async@2.6.4: - dependencies: - lodash: 4.18.1 - - async@3.2.6: {} - asynckit@0.4.0: {} - atob@2.1.2: {} - - attr-accept@2.2.5: {} - - autolinker@0.28.1: - dependencies: - gulp-header: 1.8.12 - autoprefixer@10.5.0(postcss@8.5.14): dependencies: browserslist: 4.28.2 @@ -15874,9 +14816,8 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - aws-sign2@0.7.0: {} - - aws4@1.13.2: {} + aws-ssl-profiles@1.1.2: + optional: true axios@1.16.0: dependencies: @@ -15921,6 +14862,7 @@ snapshots: '@babel/runtime': 7.29.2 cosmiconfig: 7.1.0 resolve: 1.22.12 + optional: true babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): dependencies: @@ -15951,30 +14893,10 @@ snapshots: balanced-match@4.0.4: {} - base64-arraybuffer@1.0.2: {} - - base64-js@0.0.8: {} - base64-js@1.5.1: {} - base@0.11.2: - dependencies: - cache-base: 1.0.1 - class-utils: 0.3.6 - component-emitter: 1.3.1 - define-property: 1.0.0 - isobject: 3.0.1 - mixin-deep: 1.3.2 - pascalcase: 0.1.1 - baseline-browser-mapping@2.10.29: {} - batch@0.6.1: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - bcrypt@5.1.1: dependencies: '@mapbox/node-pre-gyp': 1.0.11 @@ -15983,63 +14905,14 @@ snapshots: - encoding - supports-color - bidi-js@1.0.3: - dependencies: - require-from-string: 2.0.2 - - big-integer@1.6.52: {} - - big.js@5.2.2: {} - - binary-extensions@1.13.1: {} - binary-extensions@2.3.0: {} - binary@0.3.0: - dependencies: - buffers: 0.1.1 - chainsaw: 0.1.0 - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - optional: true - bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - block-stream2@2.1.0: - dependencies: - readable-stream: 3.6.2 - - bluebird@3.4.7: {} - - bmp-js@0.1.0: {} - - bn.js@4.12.3: {} - - bn.js@5.2.3: {} - - body-parser@1.20.5(supports-color@5.5.0): - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9(supports-color@5.5.0) - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.1 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.15.1 - raw-body: 2.5.3 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -16054,17 +14927,6 @@ snapshots: transitivePeerDependencies: - supports-color - bonjour@3.5.1: - dependencies: - array-flatten: 2.1.2 - deep-equal: 1.1.2 - dns-equal: 1.0.0 - dns-txt: 2.0.2 - multicast-dns: 7.2.5 - multicast-dns-service-types: 1.1.0 - - boolbase@1.0.0: {} - brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -16128,58 +14990,6 @@ snapshots: dependencies: fill-range: 7.1.1 - brorand@1.1.0: {} - - brotli@1.3.3: - dependencies: - base64-js: 1.5.1 - - browser-or-node@2.1.1: {} - - browserify-aes@1.2.0: - dependencies: - buffer-xor: 1.0.3 - cipher-base: 1.0.7 - create-hash: 1.2.0 - evp_bytestokey: 1.0.3 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - browserify-cipher@1.0.1: - dependencies: - browserify-aes: 1.2.0 - browserify-des: 1.0.2 - evp_bytestokey: 1.0.3 - - browserify-des@1.0.2: - dependencies: - cipher-base: 1.0.7 - des.js: 1.1.0 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - browserify-rsa@4.1.1: - dependencies: - bn.js: 5.2.3 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - - browserify-sign@4.2.5: - dependencies: - bn.js: 5.2.3 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - create-hmac: 1.1.7 - elliptic: 6.6.1 - inherits: 2.0.4 - parse-asn1: 5.1.9 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - - browserify-zlib@0.2.0: - dependencies: - pako: 1.0.11 - browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.29 @@ -16196,24 +15006,10 @@ snapshots: dependencies: node-int64: 0.4.0 - buffer-crc32@0.2.13: {} - buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} - buffer-indexof-polyfill@1.0.2: {} - - buffer-indexof@1.1.1: {} - - buffer-xor@1.0.3: {} - - buffer@4.9.2: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - isarray: 1.0.0 - buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -16224,29 +15020,28 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - buffers@0.1.1: {} - - builtin-status-codes@3.0.0: {} - busboy@1.6.0: dependencies: streamsearch: 1.1.0 bytes@3.1.2: {} - cac@6.7.14: {} - - cache-base@1.0.1: + c12@3.1.0: dependencies: - collection-visit: 1.0.0 - component-emitter: 1.3.1 - get-value: 2.0.6 - has-value: 1.0.0 - isobject: 3.0.1 - set-value: 2.0.1 - to-object-path: 0.3.0 - union-value: 1.0.1 - unset-value: 1.0.0 + chokidar: 4.0.3 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.1 + rc9: 2.1.2 + + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: dependencies: @@ -16269,19 +15064,6 @@ snapshots: camelcase-css@2.0.1: {} - camelcase-keys@2.1.0: - dependencies: - camelcase: 2.1.1 - map-obj: 1.0.1 - - camelcase@1.2.1: {} - - camelcase@2.1.1: {} - - camelcase@3.0.0: {} - - camelcase@4.1.0: {} - camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -16320,10 +15102,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chainsaw@0.1.0: - dependencies: - traverse: 0.3.9 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -16331,51 +15109,12 @@ snapshots: chalk@5.6.2: {} - change-case@5.4.4: {} - char-regex@1.0.2: {} chardet@2.1.1: {} check-error@2.1.3: {} - chokidar@2.1.8(supports-color@4.5.0): - dependencies: - anymatch: 2.0.0(supports-color@4.5.0) - async-each: 1.0.6 - braces: 2.3.2(supports-color@4.5.0) - glob-parent: 3.1.0 - inherits: 2.0.4 - is-binary-path: 1.0.1 - is-glob: 4.0.3 - normalize-path: 3.0.0 - path-is-absolute: 1.0.1 - readdirp: 2.2.1(supports-color@4.5.0) - upath: 1.2.0 - optionalDependencies: - fsevents: 1.2.13 - transitivePeerDependencies: - - supports-color - optional: true - - chokidar@2.1.8(supports-color@5.5.0): - dependencies: - anymatch: 2.0.0(supports-color@5.5.0) - async-each: 1.0.6 - braces: 2.3.2(supports-color@5.5.0) - glob-parent: 3.1.0 - inherits: 2.0.4 - is-binary-path: 1.0.1 - is-glob: 4.0.3 - normalize-path: 3.0.0 - path-is-absolute: 1.0.1 - readdirp: 2.2.1(supports-color@5.5.0) - upath: 1.2.0 - optionalDependencies: - fsevents: 1.2.13 - transitivePeerDependencies: - - supports-color - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -16398,33 +15137,22 @@ snapshots: ci-info@3.9.0: {} - cipher-base@1.0.7: + citty@0.1.6: dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 + consola: 3.4.2 + + citty@0.2.2: {} cjs-module-lexer@1.4.3: {} class-transformer@0.5.1: {} - class-utils@0.3.6: - dependencies: - arr-union: 3.1.0 - define-property: 0.2.5 - isobject: 3.0.1 - static-extend: 0.1.2 - class-validator@0.14.4: dependencies: '@types/validator': 13.15.10 libphonenumber-js: 1.13.1 validator: 13.15.35 - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -16448,18 +15176,6 @@ snapshots: cli-width@4.1.0: {} - cliui@2.1.0: - dependencies: - center-align: 0.1.3 - right-align: 0.1.3 - wordwrap: 0.0.2 - - cliui@3.2.0: - dependencies: - string-width: 1.0.2 - strip-ansi: 3.0.1 - wrap-ansi: 2.1.0 - cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -16472,16 +15188,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - clone@1.0.4: {} - clone@2.1.2: {} - clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): @@ -16498,29 +15206,14 @@ snapshots: co@4.6.0: {} - code-point-at@1.1.0: {} - - codepage@1.15.0: {} - collect-v8-coverage@1.0.3: {} - collection-visit@1.0.0: - dependencies: - map-visit: 1.0.0 - object-visit: 1.0.1 - color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} - color-name@2.1.0: {} - - color-string@2.1.4: - dependencies: - color-name: 2.1.0 - color-support@1.1.3: {} colorette@2.0.20: {} @@ -16547,29 +15240,6 @@ snapshots: component-emitter@1.3.1: {} - compress-commons@4.1.2: - dependencies: - buffer-crc32: 0.2.13 - crc32-stream: 4.0.3 - normalize-path: 3.0.0 - readable-stream: 3.6.2 - - compressible@2.0.18: - dependencies: - mime-db: 1.54.0 - - compression@1.8.1(supports-color@5.5.0): - dependencies: - bytes: 3.1.2 - compressible: 2.0.18 - debug: 2.6.9(supports-color@5.5.0) - negotiator: 0.6.4 - on-headers: 1.1.0 - safe-buffer: 5.2.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - concat-map@0.0.1: {} concat-stream@2.0.0: @@ -16579,24 +15249,12 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - concat-with-sourcemaps@1.1.0: - dependencies: - source-map: 0.6.1 - - connect-history-api-fallback@1.6.0: {} + confbox@0.2.4: {} consola@3.4.2: {} - console-browserify@1.2.0: {} - console-control-strings@1.1.0: {} - constants-browserify@1.0.0: {} - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -16616,29 +15274,14 @@ snapshots: meow: 12.1.1 split2: 4.2.0 - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} - cookie-signature@1.0.7: {} - cookie-signature@1.2.2: {} cookie@0.7.2: {} - cookie@1.1.1: {} - cookiejar@2.1.4: {} - copy-descriptor@0.1.1: {} - - core-js@3.49.0: - optional: true - - core-util-is@1.0.2: {} - - core-util-is@1.0.3: {} - cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -16658,6 +15301,7 @@ snapshots: parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.3 + optional: true cosmiconfig@8.3.6(typescript@5.9.3): dependencies: @@ -16677,42 +15321,6 @@ snapshots: optionalDependencies: typescript: 5.9.3 - crc-32@1.2.2: {} - - crc32-stream@4.0.3: - dependencies: - crc-32: 1.2.2 - readable-stream: 3.6.2 - - create-ecdh@4.0.4: - dependencies: - bn.js: 4.12.3 - elliptic: 6.6.1 - - create-frame@1.0.0: - dependencies: - define-property: 0.2.5 - extend-shallow: 2.0.1 - isobject: 3.0.1 - lazy-cache: 2.0.2 - - create-hash@1.2.0: - dependencies: - cipher-base: 1.0.7 - inherits: 2.0.4 - md5.js: 1.3.5 - ripemd160: 2.0.3 - sha.js: 2.4.12 - - create-hmac@1.1.7: - dependencies: - cipher-base: 1.0.7 - create-hash: 1.2.0 - inherits: 2.0.4 - ripemd160: 2.0.3 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - create-jest@29.7.0(@types/node@20.19.41)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: '@jest/types': 29.6.3 @@ -16735,52 +15343,12 @@ snapshots: '@types/luxon': 3.7.1 luxon: 3.7.2 - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crypto-browserify@3.12.1: - dependencies: - browserify-cipher: 1.0.1 - browserify-sign: 4.2.5 - create-ecdh: 4.0.4 - create-hash: 1.2.0 - create-hmac: 1.1.7 - diffie-hellman: 5.0.3 - hash-base: 3.0.5 - inherits: 2.0.4 - pbkdf2: 3.1.5 - public-encrypt: 4.0.3 - randombytes: 2.1.0 - randomfill: 1.0.4 - - css-line-break@2.1.0: - dependencies: - utrie: 1.0.2 - - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-what@6.2.2: {} - cssesc@3.0.0: {} cssstyle@4.6.0: @@ -16790,59 +15358,8 @@ snapshots: csstype@3.2.3: {} - currently-unhandled@0.4.1: - dependencies: - array-find-index: 1.0.2 - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-color@3.1.0: {} - - d3-ease@3.0.1: {} - - d3-format@3.1.2: {} - - d3-interpolate@3.0.1: - dependencies: - d3-color: 3.1.0 - - d3-path@3.1.0: {} - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - - d3-timer@3.0.1: {} - - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.3 - dargs@8.1.0: {} - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -16893,13 +15410,13 @@ snapshots: debug@3.1.0: dependencies: - ms: 2.0.0 + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 debug@3.2.7(supports-color@5.5.0): dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 debug@4.4.3(supports-color@5.5.0): dependencies: @@ -16909,8 +15426,6 @@ snapshots: decamelize@1.2.0: {} - decimal.js-light@2.5.1: {} - decimal.js@10.6.0: {} decode-uri-component@0.2.2: {} @@ -16919,26 +15434,13 @@ snapshots: optionalDependencies: babel-plugin-macros: 3.1.0 - deep-diff@1.0.2: {} - deep-eql@5.0.2: {} - deep-equal@1.1.2: - dependencies: - is-arguments: 1.2.0 - is-date-object: 1.1.0 - is-regex: 1.2.1 - object-is: 1.1.6 - object-keys: 1.1.1 - regexp.prototype.flags: 1.5.4 - deep-is@0.1.4: {} - deepmerge@4.3.1: {} + deepmerge-ts@7.1.5: {} - default-compare@1.0.0: - dependencies: - kind-of: 5.1.0 + deepmerge@4.3.1: {} defaults@1.0.4: dependencies: @@ -16956,88 +15458,38 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - define-property@0.2.5: - dependencies: - is-descriptor: 0.1.8 - - define-property@1.0.0: - dependencies: - is-descriptor: 1.0.4 - - define-property@2.0.2: - dependencies: - is-descriptor: 1.0.4 - isobject: 3.0.1 - - del@3.0.0: - dependencies: - globby: 6.1.0 - is-path-cwd: 1.0.0 - is-path-in-cwd: 1.0.1 - p-map: 1.2.0 - pify: 3.0.0 - rimraf: 2.7.1 + defu@6.1.7: {} delayed-stream@1.0.0: {} delegates@1.0.0: {} - depd@1.1.2: {} + denque@2.1.0: + optional: true depd@2.0.0: {} - dequal@2.0.3: {} - - des.js@1.1.0: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - destr@2.0.5: {} - destroy@1.2.0: {} - detect-libc@2.1.2: {} detect-newline@3.1.0: {} - detect-node-es@1.1.0: {} - - detect-node@2.1.0: {} - dezalgo@1.0.4: dependencies: asap: 2.0.6 wrappy: 1.0.2 - dfa@1.2.0: {} - didyoumean@1.2.2: {} diff-sequences@29.6.3: {} diff@4.0.4: {} - diffie-hellman@5.0.3: - dependencies: - bn.js: 4.12.3 - miller-rabin: 4.0.1 - randombytes: 2.1.0 - dijkstrajs@1.0.3: {} dlv@1.1.3: {} - dns-equal@1.0.0: {} - - dns-packet@5.6.1: - dependencies: - '@leichtgewicht/ip-codec': 2.0.5 - - dns-txt@2.0.2: - dependencies: - buffer-indexof: 1.1.1 - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -17096,114 +15548,48 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexer2@0.1.4: - dependencies: - readable-stream: 2.3.8 - eastasianwidth@0.2.0: {} - ebec@1.1.1: - dependencies: - smob: 1.6.1 - - ebec@2.3.0: {} - - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 ee-first@1.1.1: {} + effect@3.21.0: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.353: {} - elliptic@6.6.1: - dependencies: - bn.js: 4.12.3 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - emittery@0.13.1: {} - emoji-regex-xs@1.0.0: {} - emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - emojis-list@3.0.0: {} + empathic@2.0.0: {} encodeurl@2.0.0: {} - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - engine.io-client@6.6.4: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3(supports-color@5.5.0) - engine.io-parser: 5.2.3 - ws: 8.18.3 - xmlhttprequest-ssl: 2.1.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - engine.io-parser@5.2.3: {} - - enhanced-resolve@3.4.1: - dependencies: - graceful-fs: 4.2.11 - memory-fs: 0.4.1 - object-assign: 4.1.1 - tapable: 0.2.9 - enhanced-resolve@5.21.3: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 - ent@2.2.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - punycode: 1.4.1 - safe-regex-test: 1.1.0 - - entities@4.5.0: {} - entities@6.0.1: {} env-paths@2.2.1: {} environment@1.1.0: {} - envix@1.5.0: - dependencies: - std-env: 3.10.0 - - errno@0.1.8: - dependencies: - prr: 1.0.1 - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - error-symbol@0.1.0: {} - es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 @@ -17309,51 +15695,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.46.1: {} - - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - - es6-map@0.1.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - es6-set: 0.1.6 - es6-symbol: 3.1.4 - event-emitter: 0.3.5 - - es6-set@0.1.6: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - event-emitter: 0.3.5 - type: 2.7.3 - - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - - es6-weak-map@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -17388,13 +15729,6 @@ snapshots: escape-string-regexp@4.0.0: {} - escope@3.6.0: - dependencies: - es6-map: 0.1.5 - es6-weak-map: 2.0.3 - esrecurse: 4.3.0 - estraverse: 4.3.0 - eslint-config-prettier@9.1.2(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -17533,13 +15867,6 @@ snapshots: transitivePeerDependencies: - supports-color - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.3 - espree@9.6.1: dependencies: acorn: 8.16.0 @@ -17568,58 +15895,12 @@ snapshots: etag@1.8.1: {} - ethiopian-calendar-date-converter@2.1.6: {} - - ethiopian-calendar-new@1.1.0: {} - - ethiopian-date@0.0.6: {} - - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - - event-target-shim@5.0.1: {} - eventemitter2@6.4.9: {} - eventemitter3@4.0.7: {} - eventemitter3@5.0.4: {} events@3.3.0: {} - eventsource@0.1.6: - dependencies: - original: 1.0.2 - - evp_bytestokey@1.0.3: - dependencies: - md5.js: 1.3.5 - safe-buffer: 5.2.1 - - exceljs@4.4.0: - dependencies: - archiver: 5.3.2 - dayjs: 1.11.20 - fast-csv: 4.3.6 - jszip: 3.10.1 - readable-stream: 3.6.2 - saxes: 5.0.1 - tmp: 0.2.5 - unzipper: 0.10.14 - uuid: 8.3.2 - - execa@0.7.0: - dependencies: - cross-spawn: 5.1.0 - get-stream: 3.0.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -17693,42 +15974,6 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express@4.22.2(supports-color@5.5.0): - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.5(supports-color@5.5.0) - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.0.7 - debug: 2.6.9(supports-color@5.5.0) - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.2(supports-color@5.5.0) - fresh: 0.5.2 - http-errors: 2.0.1 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.13 - proxy-addr: 2.0.7 - qs: 6.15.1 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.2(supports-color@5.5.0) - serve-static: 1.16.3(supports-color@5.5.0) - setprototypeof: 1.2.0 - statuses: 2.0.2 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - express@5.2.1: dependencies: accepts: 2.0.0 @@ -17823,15 +16068,12 @@ snapshots: dependencies: kind-of: 5.1.0 - fast-csv@4.3.6: + fast-check@3.23.2: dependencies: - '@fast-csv/format': 4.3.5 - '@fast-csv/parse': 4.3.6 + pure-rand: 6.1.0 fast-deep-equal@3.1.3: {} - fast-equals@5.4.0: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -17844,37 +16086,14 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-png@6.4.0: - dependencies: - '@types/pako': 2.0.4 - iobuffer: 5.4.0 - pako: 2.1.0 - fast-safe-stringify@2.1.1: {} fast-uri@3.1.2: {} - fast-xml-parser@4.5.6: - dependencies: - strnum: 1.1.2 - fastq@1.20.1: dependencies: reusify: 1.1.0 - faye-websocket@0.10.0: - dependencies: - websocket-driver: 0.7.4 - - faye-websocket@0.11.4: - dependencies: - websocket-driver: 0.7.4 - - faye@0.8.11: - dependencies: - cookiejar: 2.1.4 - faye-websocket: 0.11.4 - fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -17883,22 +16102,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 - fflate@0.8.2: {} - file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - file-selector@2.1.2: - dependencies: - tslib: 2.8.1 - - file-type@18.7.0: - dependencies: - readable-web-to-node-stream: 3.0.4 - strtok3: 7.1.1 - token-types: 5.0.1 - file-type@21.3.4: dependencies: '@tokenizer/inflate': 0.4.1 @@ -17908,34 +16115,10 @@ snapshots: transitivePeerDependencies: - supports-color - file-uri-to-path@1.0.0: - optional: true - - fill-range@4.0.0: - dependencies: - extend-shallow: 2.0.1 - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range: 2.1.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - filter-obj@1.1.0: {} - - finalhandler@1.3.2(supports-color@5.5.0): - dependencies: - debug: 2.6.9(supports-color@5.5.0) - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - finalhandler@2.1.1: dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -17947,17 +16130,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-root@1.1.0: {} - - find-up@1.1.2: - dependencies: - path-exists: 2.1.0 - pinkie-promise: 2.0.1 - - find-up@2.1.0: - dependencies: - locate-path: 2.0.0 - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -17980,8 +16152,6 @@ snapshots: keyv: 4.5.4 rimraf: 3.0.2 - flat@5.0.2: {} - flatted@3.4.2: {} follow-redirects@1.16.0(debug@3.2.7(supports-color@5.5.0)): @@ -18004,25 +16174,11 @@ snapshots: dependencies: is-callable: 1.2.7 - for-in@1.0.2: {} - - for-own@1.0.0: - dependencies: - for-in: 1.0.2 - - force@0.0.3: - dependencies: - faye: 0.8.11 - mime: 1.2.11 - request: 2.88.2 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0): dependencies: '@babel/code-frame': 7.29.0 @@ -18040,12 +16196,6 @@ snapshots: typescript: 5.9.3 webpack: 5.106.0 - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -18062,8 +16212,6 @@ snapshots: forwarded@0.2.0: {} - frac@1.1.2: {} - fraction.js@5.3.4: {} fragment-cache@0.2.1: @@ -18084,10 +16232,6 @@ snapshots: fresh@2.0.0: {} - fs-constants@1.0.0: {} - - fs-exists-sync@0.1.0: {} - fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -18102,22 +16246,9 @@ snapshots: fs.realpath@1.0.0: {} - fsevents@1.2.13: - dependencies: - bindings: 1.5.0 - nan: 2.26.2 - optional: true - fsevents@2.3.3: optional: true - fstream@1.0.12: - dependencies: - graceful-fs: 4.2.11 - inherits: 2.0.4 - mkdirp: 0.5.6 - rimraf: 2.7.1 - function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -18143,12 +16274,15 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + optional: true + generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} - get-caller-file@1.0.3: {} - get-caller-file@2.0.5: {} get-east-asian-width@1.6.0: {} @@ -18166,13 +16300,6 @@ snapshots: hasown: 2.0.3 math-intrinsics: 1.1.0 - get-nonce@1.0.1: {} - - get-object@0.2.0: - dependencies: - is-number: 2.1.0 - isobject: 0.2.0 - get-package-type@0.1.0: {} get-proto@1.0.1: @@ -18180,10 +16307,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stdin@4.0.1: {} - - get-stream@3.0.0: {} - get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -18194,11 +16317,14 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-value@2.0.6: {} - - getpass@0.1.7: + giget@2.0.0: dependencies: - assert-plus: 1.0.0 + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.7 + node-fetch-native: 1.6.7 + nypm: 0.6.6 + pathe: 2.0.3 git-raw-commits@4.0.0: dependencies: @@ -18206,11 +16332,6 @@ snapshots: meow: 12.1.1 split2: 4.2.0 - glob-parent@3.1.0: - dependencies: - is-glob: 3.1.0 - path-dirname: 1.0.2 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -18258,71 +16379,12 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - globby@6.1.0: - dependencies: - array-union: 1.0.2 - glob: 7.2.3 - object-assign: 4.1.1 - pify: 2.3.0 - pinkie-promise: 2.0.1 - gopd@1.2.0: {} graceful-fs@4.2.11: {} graphemer@1.4.0: {} - gulp-header@1.8.12: - dependencies: - concat-with-sourcemaps: 1.1.0 - lodash.template: 4.18.1 - through2: 2.0.5 - - handle-thing@2.0.1: {} - - handlebars-helper-create-frame@0.1.0: - dependencies: - create-frame: 1.0.0 - isobject: 3.0.1 - - handlebars-helpers@0.10.0: - dependencies: - arr-flatten: 1.1.0 - array-sort: 0.1.4 - create-frame: 1.0.0 - define-property: 1.0.0 - falsey: 0.3.2 - for-in: 1.0.2 - for-own: 1.0.0 - get-object: 0.2.0 - get-value: 2.0.6 - handlebars: 4.7.9 - handlebars-helper-create-frame: 0.1.0 - handlebars-utils: 1.0.6 - has-value: 1.0.0 - helper-date: 1.0.1 - helper-markdown: 1.0.0 - helper-md: 0.2.2 - html-tag: 2.0.0 - is-even: 1.0.0 - is-glob: 4.0.3 - is-number: 4.0.0 - kind-of: 6.0.3 - lazy-cache: 2.0.2 - logging-helpers: 1.0.0 - micromatch: 3.1.10 - relative: 3.0.2 - striptags: 3.2.0 - to-gfm-code-block: 0.1.1 - year: 0.2.1 - transitivePeerDependencies: - - supports-color - - handlebars-utils@1.0.6: - dependencies: - kind-of: 6.0.3 - typeof-article: 0.1.1 - handlebars@4.7.9: dependencies: minimist: 1.2.8 @@ -18332,19 +16394,8 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - har-schema@2.0.0: {} - - har-validator@5.1.5: - dependencies: - ajv: 6.15.0 - har-schema: 2.0.0 - has-bigints@1.1.0: {} - has-flag@2.0.0: {} - - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -18363,128 +16414,16 @@ snapshots: has-unicode@2.0.1: {} - has-value@0.3.1: - dependencies: - get-value: 2.0.6 - has-values: 0.1.4 - isobject: 2.1.0 - - has-value@1.0.0: - dependencies: - get-value: 2.0.6 - has-values: 1.0.0 - isobject: 3.0.1 - - has-values@0.1.4: {} - - has-values@1.0.0: - dependencies: - is-number: 3.0.0 - kind-of: 4.0.0 - - hash-base@3.0.5: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - - hash-base@3.1.2: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - hasown@2.0.3: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - - helper-date@1.0.1: - dependencies: - date.js: 0.3.3 - handlebars-utils: 1.0.6 - moment: 2.30.1 - transitivePeerDependencies: - - supports-color - - helper-markdown@1.0.0: - dependencies: - handlebars-utils: 1.0.6 - highlight.js: 9.18.5 - remarkable: 1.7.4 - - helper-md@0.2.2: - dependencies: - ent: 2.2.2 - extend-shallow: 2.0.1 - fs-exists-sync: 0.1.0 - remarkable: 1.7.4 - - highlight.js@9.18.5: {} - - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - - hosted-git-info@2.8.9: {} - - hpack.js@2.1.6: - dependencies: - inherits: 2.0.4 - obuf: 1.1.2 - readable-stream: 2.3.8 - wbuf: 1.7.3 - - hsl-to-hex@1.0.0: - dependencies: - hsl-to-rgb-for-reals: 1.1.1 - - hsl-to-rgb-for-reals@1.1.1: {} - html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 - html-entities@1.4.0: {} - html-escaper@2.0.2: {} - html-parse-stringify@3.0.1: - dependencies: - void-elements: 3.1.0 - - html-tag@2.0.0: - dependencies: - is-self-closing: 1.0.1 - kind-of: 6.0.3 - - html2canvas@1.4.1: - dependencies: - css-line-break: 2.1.0 - text-segmentation: 1.0.3 - - http-deceiver@1.2.7: {} - - http-errors@1.8.1: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -18493,8 +16432,6 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-parser-js@0.5.10: {} - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -18502,32 +16439,6 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-middleware@0.19.2(debug@3.2.7(supports-color@5.5.0))(supports-color@5.5.0): - dependencies: - http-proxy: 1.18.1(debug@3.2.7(supports-color@5.5.0)) - is-glob: 4.0.3 - lodash: 4.18.1 - micromatch: 3.1.10(supports-color@5.5.0) - transitivePeerDependencies: - - debug - - supports-color - - http-proxy@1.18.1(debug@3.2.7(supports-color@5.5.0)): - dependencies: - eventemitter3: 4.0.7 - follow-redirects: 1.16.0(debug@3.2.7(supports-color@5.5.0)) - requires-port: 1.0.0 - transitivePeerDependencies: - - debug - - http-signature@1.2.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.18.0 - - https-browserify@1.0.0: {} - https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -18548,22 +16459,6 @@ snapshots: husky@9.1.7: {} - hyphen@1.14.1: {} - - i18next-browser-languagedetector@8.2.1: - dependencies: - '@babel/runtime': 7.29.2 - - i18next@25.10.10(typescript@5.9.3): - dependencies: - '@babel/runtime': 7.29.2 - optionalDependencies: - typescript: 5.9.3 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -18572,30 +16467,20 @@ snapshots: dependencies: safer-buffer: 2.1.2 - idb-keyval@6.2.2: {} - ieee754@1.2.1: {} ignore@5.3.2: {} ignore@7.0.5: {} - immediate@3.0.6: {} - - immer@10.2.0: {} - - immer@11.1.8: {} + immer@11.1.8: + optional: true import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-local@1.0.0: - dependencies: - pkg-dir: 2.0.0 - resolve-cwd: 2.0.0 - import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -18605,56 +16490,23 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@2.1.0: - dependencies: - repeating: 2.0.1 - inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - info-symbol@0.1.0: {} - - inherits@2.0.3: {} - inherits@2.0.4: {} ini@4.1.1: {} - internal-ip@1.2.0: - dependencies: - meow: 3.7.0 - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.3 side-channel: 1.1.0 - internmap@2.0.3: {} - - interpret@1.4.0: {} - - invert-kv@1.0.0: {} - - iobuffer@5.4.0: {} - - ip@1.1.9: {} - ipaddr.js@1.9.1: {} - ipaddr.js@2.4.0: {} - - is-accessor-descriptor@1.0.2: - dependencies: - hasown: 2.0.3 - - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -18675,10 +16527,6 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-binary-path@1.0.1: - dependencies: - binary-extensions: 1.13.1 - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -18688,18 +16536,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-buffer@1.1.6: {} - is-callable@1.2.7: {} is-core-module@2.16.2: dependencies: hasown: 2.0.3 - is-data-descriptor@1.0.1: - dependencies: - hasown: 2.0.3 - is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -18711,40 +16553,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-descriptor@0.1.8: - dependencies: - is-accessor-descriptor: 1.0.2 - is-data-descriptor: 1.0.1 - - is-descriptor@1.0.4: - dependencies: - is-accessor-descriptor: 1.0.2 - is-data-descriptor: 1.0.1 - - is-even@1.0.0: - dependencies: - is-odd: 0.1.2 - - is-extendable@0.1.1: {} - - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 - is-finite@1.1.0: {} - - is-fullwidth-code-point@1.0.0: - dependencies: - number-is-nan: 1.0.1 - - is-fullwidth-code-point@2.0.0: {} - is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@4.0.0: {} @@ -18763,20 +16577,12 @@ snapshots: has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - is-glob@3.1.0: - dependencies: - is-extglob: 2.1.1 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-interactive@1.0.0: {} - is-lite@0.8.2: {} - - is-lite@1.2.1: {} - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -18786,44 +16592,19 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@2.1.0: - dependencies: - kind-of: 3.2.2 - - is-number@3.0.0: - dependencies: - kind-of: 3.2.2 - - is-number@4.0.0: {} - is-number@7.0.0: {} is-obj@2.0.0: {} - is-odd@0.1.2: - dependencies: - is-number: 3.0.0 - - is-path-cwd@1.0.0: {} - - is-path-in-cwd@1.0.1: - dependencies: - is-path-inside: 1.0.1 - - is-path-inside@1.0.1: - dependencies: - path-is-inside: 1.0.2 - is-path-inside@3.0.3: {} - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - is-potential-custom-element-name@1.0.1: {} is-promise@4.0.0: {} + is-property@1.0.2: + optional: true + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -18831,18 +16612,12 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.3 - is-self-closing@1.0.1: - dependencies: - self-closing-tags: 1.0.1 - is-set@2.0.3: {} is-shared-array-buffer@1.0.4: dependencies: call-bound: 1.0.4 - is-stream@1.1.0: {} - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -18866,14 +16641,8 @@ snapshots: dependencies: which-typed-array: 1.1.20 - is-typedarray@1.0.0: {} - is-unicode-supported@0.1.0: {} - is-url@1.2.4: {} - - is-utf8@0.2.1: {} - is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -18885,26 +16654,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-windows@1.0.2: {} - - is-wsl@1.1.0: {} - - isarray@1.0.0: {} - isarray@2.0.5: {} isexe@2.0.0: {} - isobject@0.2.0: {} - - isobject@2.1.0: - dependencies: - isarray: 1.0.0 - - isobject@3.0.1: {} - - isstream@0.1.2: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -18963,10 +16716,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jay-peg@1.1.1: - dependencies: - restructure: 3.0.2 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -19286,18 +17035,6 @@ snapshots: jiti@2.6.1: {} - jiti@2.7.0: {} - - jmespath@0.16.0: {} - - jose@5.10.0: {} - - jquery@3.7.1: {} - - js-cookie@3.0.5: {} - - js-md5@0.8.3: {} - js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -19347,26 +17084,14 @@ snapshots: json-buffer@3.0.1: {} - json-loader@0.5.7: {} - json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} - json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json-stream@1.0.0: {} - - json-stringify-safe@5.0.1: {} - - json3@3.3.3: {} - - json5@0.5.1: {} - json5@1.0.2: dependencies: minimist: 1.2.8 @@ -19409,24 +17134,6 @@ snapshots: ms: 2.1.3 semver: 7.8.0 - jspdf@3.0.4: - dependencies: - '@babel/runtime': 7.29.2 - fast-png: 6.4.0 - fflate: 0.8.2 - optionalDependencies: - canvg: 3.0.11 - core-js: 3.49.0 - dompurify: 3.4.2 - html2canvas: 1.4.1 - - jsprim@1.4.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -19434,13 +17141,6 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - jwa@1.4.2: dependencies: buffer-equal-constant-time: 1.0.1 @@ -19467,36 +17167,8 @@ snapshots: dependencies: json-buffer: 3.0.1 - killable@1.0.1: {} - - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - - kind-of@4.0.0: - dependencies: - is-buffer: 1.1.6 - - kind-of@5.1.0: {} - - kind-of@6.0.3: {} - kleur@3.0.3: {} - lazy-cache@1.0.4: {} - - lazy-cache@2.0.2: - dependencies: - set-getter: 0.1.1 - - lazystream@1.0.1: - dependencies: - readable-stream: 2.3.8 - - lcid@1.0.0: - dependencies: - invert-kv: 1.0.0 - leven@3.1.0: {} levn@0.4.1: @@ -19506,15 +17178,6 @@ snapshots: libphonenumber-js@1.13.1: {} - libreoffice-convert@1.8.1: - dependencies: - async: 3.2.6 - tmp: 0.2.5 - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - lightningcss-android-arm64@1.32.0: optional: true @@ -19563,18 +17226,12 @@ snapshots: lightningcss-linux-x64-musl: 1.32.0 lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + optional: true lilconfig@3.1.3: {} - linebreak@1.1.0: - dependencies: - base64-js: 0.0.8 - unicode-trie: 2.0.0 - lines-and-columns@1.2.4: {} - linkifyjs@4.3.2: {} - lint-staged@15.5.2: dependencies: chalk: 5.6.2 @@ -19590,8 +17247,6 @@ snapshots: transitivePeerDependencies: - supports-color - listenercount@1.0.1: {} - listr2@8.3.3: dependencies: cli-truncate: 4.0.0 @@ -19603,38 +17258,8 @@ snapshots: load-esm@1.0.3: {} - load-json-file@1.1.0: - dependencies: - graceful-fs: 4.2.11 - parse-json: 2.2.0 - pify: 2.3.0 - pinkie-promise: 2.0.1 - strip-bom: 2.0.0 - - load-json-file@2.0.0: - dependencies: - graceful-fs: 4.2.11 - parse-json: 2.2.0 - pify: 2.3.0 - strip-bom: 3.0.0 - - loadash@1.0.0: {} - - loader-runner@2.4.0: {} - loader-runner@4.3.2: {} - loader-utils@1.4.2: - dependencies: - big.js: 5.2.2 - emojis-list: 3.0.0 - json5: 1.0.2 - - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -19647,49 +17272,20 @@ snapshots: dependencies: p-locate: 6.0.0 - locter@2.2.1: - dependencies: - destr: 2.0.5 - ebec: 2.3.0 - fast-glob: 3.3.3 - flat: 5.0.2 - jiti: 2.7.0 - yaml: 2.9.0 - - lodash._reinterpolate@3.0.0: {} - lodash.camelcase@4.3.0: {} - lodash.defaults@4.2.0: {} - - lodash.difference@4.5.0: {} - - lodash.escaperegexp@4.1.2: {} - - lodash.flatten@4.4.0: {} - - lodash.groupby@4.6.0: {} - lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} - lodash.isequal@4.5.0: {} - - lodash.isfunction@3.0.9: {} - lodash.isinteger@4.0.4: {} - lodash.isnil@4.0.0: {} - lodash.isnumber@3.0.3: {} lodash.isplainobject@4.0.6: {} lodash.isstring@4.0.1: {} - lodash.isundefined@3.0.1: {} - lodash.kebabcase@4.1.1: {} lodash.memoize@4.1.2: {} @@ -19704,17 +17300,6 @@ snapshots: lodash.startcase@4.4.0: {} - lodash.template@4.18.1: - dependencies: - lodash._reinterpolate: 3.0.0 - lodash.templatesettings: 4.2.0 - - lodash.templatesettings@4.2.0: - dependencies: - lodash._reinterpolate: 3.0.0 - - lodash.union@4.6.0: {} - lodash.uniq@4.5.0: {} lodash.upperfirst@4.3.1: {} @@ -19723,11 +17308,6 @@ snapshots: lodash@4.18.1: {} - log-ok@0.1.1: - dependencies: - ansi-green: 0.1.1 - success-symbol: 0.1.0 - log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -19741,51 +17321,19 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 - log-utils@0.2.1: - dependencies: - ansi-colors: 0.2.0 - error-symbol: 0.1.0 - info-symbol: 0.1.0 - log-ok: 0.1.1 - success-symbol: 0.1.0 - time-stamp: 1.1.0 - warning-symbol: 0.1.0 - - logging-helpers@1.0.0: - dependencies: - isobject: 3.0.1 - log-utils: 0.2.1 - - loglevel@1.9.2: {} - - longest@1.0.1: {} + long@5.3.2: + optional: true loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - lottie-web@5.13.0: {} - - loud-rejection@1.6.0: - dependencies: - currently-unhandled: 0.4.1 - signal-exit: 3.0.7 - loupe@3.2.1: {} - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - lru-cache@10.4.3: {} lru-cache@11.3.6: {} - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -19804,8 +17352,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-cancellable-promise@2.0.0: {} - make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -19816,72 +17362,24 @@ snapshots: make-error@1.3.6: {} - make-event-props@2.0.0: {} - makeerror@1.0.12: dependencies: tmpl: 1.0.5 - map-cache@0.2.2: {} - - map-obj@1.0.1: {} - - map-visit@1.0.0: - dependencies: - object-visit: 1.0.1 - math-intrinsics@1.1.0: {} - md5.js@1.3.5: - dependencies: - hash-base: 3.0.5 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - mdn-data@2.0.14: {} - - media-engine@1.0.3: {} - media-typer@0.3.0: {} media-typer@1.1.0: {} - mem@1.1.0: - dependencies: - mimic-fn: 1.2.0 - memfs@3.5.3: dependencies: fs-monkey: 1.1.0 - memory-fs@0.4.1: - dependencies: - errno: 0.1.8 - readable-stream: 2.3.8 - meow@12.1.1: {} - meow@3.7.0: - dependencies: - camelcase-keys: 2.1.0 - decamelize: 1.2.0 - loud-rejection: 1.6.0 - map-obj: 1.0.1 - minimist: 1.2.8 - normalize-package-data: 2.5.0 - object-assign: 4.1.1 - read-pkg-up: 1.0.1 - redent: 1.0.0 - trim-newlines: 1.0.0 - - merge-descriptors@1.0.3: {} - merge-descriptors@2.0.0: {} - merge-refs@2.0.0(@types/react@18.3.28): - optionalDependencies: - '@types/react': 18.3.28 - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -19948,11 +17446,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - miller-rabin@4.0.1: - dependencies: - bn.js: 4.12.3 - brorand: 1.1.0 - mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -19965,14 +17458,8 @@ snapshots: dependencies: mime-db: 1.54.0 - mime@1.2.11: {} - - mime@1.6.0: {} - mime@2.6.0: {} - mimic-fn@1.2.0: {} - mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -19991,33 +17478,12 @@ snapshots: dependencies: brace-expansion: 1.1.14 - minimatch@5.1.9: - dependencies: - brace-expansion: 2.1.0 - minimatch@9.0.9: dependencies: brace-expansion: 2.1.0 minimist@1.2.8: {} - minio@7.1.3: - dependencies: - async: 3.2.6 - block-stream2: 2.1.0 - browser-or-node: 2.1.1 - buffer-crc32: 0.2.13 - fast-xml-parser: 4.5.6 - ipaddr.js: 2.4.0 - json-stream: 1.0.0 - lodash: 4.18.1 - mime-types: 2.1.35 - query-string: 7.1.3 - through2: 4.0.2 - web-encoding: 1.1.5 - xml: 1.0.1 - xml2js: 0.5.0 - minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -20031,27 +17497,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - mkdirp@1.0.4: {} - moment@2.30.1: {} - - motion-dom@12.38.0: - dependencies: - motion-utils: 12.36.0 - - motion-utils@12.36.0: {} - - ms@2.0.0: {} - ms@2.1.3: {} mui-ethiopian-datepicker@0.3.2(bb2b0a350b39c049005e46c2e15483da): @@ -20083,22 +17530,30 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 - multicast-dns-service-types@1.1.0: {} - - multicast-dns@7.2.5: - dependencies: - dns-packet: 5.6.1 - thunky: 1.1.0 - mute-stream@2.0.0: {} + mysql2@3.15.3: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + optional: true + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.26.2: + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.4 optional: true nanoid@3.3.12: {} @@ -20136,28 +17591,10 @@ snapshots: - supports-color optional: true - nanomatch@1.2.13(supports-color@5.5.0): - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - fragment-cache: 0.2.1 - is-windows: 1.0.2 - kind-of: 6.0.3 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2(supports-color@5.5.0) - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color + nanoid@3.3.12: {} natural-compare@1.4.0: {} - negotiator@0.6.3: {} - - negotiator@0.6.4: {} - negotiator@1.0.0: {} neo-async@2.6.2: {} @@ -20186,8 +17623,6 @@ snapshots: node-addon-api@5.1.0: {} - node-addon-api@8.7.0: {} - node-emoji@1.11.0: dependencies: lodash: 4.18.1 @@ -20199,74 +17634,22 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-forge@0.10.0: {} - - node-gyp-build@4.8.4: {} - - node-html-parser@6.1.13: - dependencies: - css-select: 5.2.2 - he: 1.2.0 - node-int64@0.4.0: {} - node-libs-browser@2.2.1: - dependencies: - assert: 1.5.1 - browserify-zlib: 0.2.0 - buffer: 4.9.2 - console-browserify: 1.2.0 - constants-browserify: 1.0.0 - crypto-browserify: 3.12.1 - domain-browser: 1.2.0 - events: 3.3.0 - https-browserify: 1.0.0 - os-browserify: 0.3.0 - path-browserify: 0.0.1 - process: 0.11.10 - punycode: 1.4.1 - querystring-es3: 0.2.1 - readable-stream: 2.3.8 - stream-browserify: 2.0.2 - stream-http: 2.8.3 - string_decoder: 1.3.0 - timers-browserify: 2.0.12 - tty-browserify: 0.0.0 - url: 0.11.4 - util: 0.11.1 - vm-browserify: 1.1.2 - node-releases@2.0.44: {} nopt@5.0.0: dependencies: abbrev: 1.1.1 - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.12 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-path@2.1.1: - dependencies: - remove-trailing-separator: 1.1.0 - normalize-path@3.0.0: {} - normalize-svg-path@1.1.0: - dependencies: - svg-arc-to-cubic-bezier: 3.2.0 - - npm-run-path@2.0.2: - dependencies: - path-key: 2.0.1 - npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -20282,39 +17665,22 @@ snapshots: gauge: 3.0.2 set-blocking: 2.0.0 - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - number-is-nan@1.0.1: {} - nwsapi@2.2.23: {} - oauth-sign@0.9.0: {} + nypm@0.6.6: + dependencies: + citty: 0.2.2 + pathe: 2.0.3 + tinyexec: 1.1.2 object-assign@4.1.1: {} - object-copy@0.1.0: - dependencies: - copy-descriptor: 0.1.1 - define-property: 0.2.5 - kind-of: 3.2.2 - object-hash@3.0.0: {} object-inspect@1.13.4: {} - object-is@1.1.6: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - object-keys@1.1.1: {} - object-visit@1.0.1: - dependencies: - isobject: 3.0.1 - object.assign@4.1.7: dependencies: call-bind: 1.0.9 @@ -20344,10 +17710,6 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 - object.pick@1.3.0: - dependencies: - isobject: 3.0.1 - object.values@1.2.1: dependencies: call-bind: 1.0.9 @@ -20355,14 +17717,12 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - obuf@1.1.2: {} + ohash@2.0.11: {} on-finished@2.4.1: dependencies: ee-first: 1.1.1 - on-headers@1.1.0: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -20379,12 +17739,6 @@ snapshots: dependencies: mimic-function: 5.0.1 - opencollective-postinstall@2.0.3: {} - - opn@5.5.0: - dependencies: - is-wsl: 1.1.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -20406,36 +17760,12 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - orderedmap@2.1.1: {} - - original@1.0.2: - dependencies: - url-parse: 1.5.10 - - os-browserify@0.3.0: {} - - os-locale@1.4.0: - dependencies: - lcid: 1.0.0 - - os-locale@2.1.0: - dependencies: - execa: 0.7.0 - lcid: 1.0.0 - mem: 1.1.0 - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-finally@1.0.0: {} - - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -20448,10 +17778,6 @@ snapshots: dependencies: yocto-queue: 1.2.2 - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -20464,36 +17790,14 @@ snapshots: dependencies: p-limit: 4.0.0 - p-map@1.2.0: {} - - p-try@1.0.0: {} - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} - pako@0.2.9: {} - - pako@1.0.11: {} - - pako@2.1.0: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-asn1@5.1.9: - dependencies: - asn1.js: 4.10.1 - browserify-aes: 1.2.0 - evp_bytestokey: 1.0.3 - pbkdf2: 3.1.5 - safe-buffer: 5.2.1 - - parse-json@2.2.0: - dependencies: - error-ex: 1.3.4 - parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -20501,21 +17805,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-svg-path@0.1.2: {} - parse5@7.3.0: dependencies: entities: 6.0.1 parseurl@1.3.3: {} - pascal-case@3.1.2: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - pascalcase@0.1.1: {} - passport-jwt@4.0.1: dependencies: jsonwebtoken: 9.0.3 @@ -20529,26 +17824,12 @@ snapshots: pause: 0.0.1 utils-merge: 1.0.1 - path-browserify@0.0.1: {} - - path-dirname@1.0.2: {} - - path-exists@2.1.0: - dependencies: - pinkie-promise: 2.0.1 - - path-exists@3.0.0: {} - path-exists@4.0.0: {} path-exists@5.0.0: {} path-is-absolute@1.0.1: {} - path-is-inside@1.0.2: {} - - path-key@2.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -20585,24 +17866,17 @@ snapshots: path@0.12.7: dependencies: - process: 0.11.10 - util: 0.10.4 + lru-cache: 11.3.6 + minipass: 7.1.3 + + path-to-regexp@3.3.0: {} + + path-to-regexp@8.4.2: {} + + path-type@4.0.0: {} pathe@1.1.2: {} - pathval@2.0.1: {} - - pause@0.0.1: {} - - pbkdf2@3.1.5: - dependencies: - create-hash: 1.2.0 - create-hmac: 1.1.7 - ripemd160: 2.0.3 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - to-buffer: 1.2.2 - pdfjs-dist@2.16.105: dependencies: dommatrix: 1.0.3 @@ -20612,24 +17886,28 @@ snapshots: optionalDependencies: '@napi-rs/canvas': 0.1.100 - peek-readable@5.4.2: {} + pathval@2.0.1: {} - perfect-freehand@1.2.3: {} + pause@0.0.1: {} - performance-now@2.1.0: {} + perfect-debounce@1.0.0: {} pg-cloudflare@1.3.0: optional: true - pg-connection-string@2.12.0: {} + pg-connection-string@2.12.0: + optional: true - pg-int8@1.0.1: {} + pg-int8@1.0.1: + optional: true pg-pool@3.13.0(pg@8.20.0): dependencies: pg: 8.20.0 + optional: true - pg-protocol@1.13.0: {} + pg-protocol@1.13.0: + optional: true pg-types@2.2.0: dependencies: @@ -20638,6 +17916,7 @@ snapshots: postgres-bytea: 1.0.1 postgres-date: 1.0.7 postgres-interval: 1.2.0 + optional: true pg@8.20.0: dependencies: @@ -20648,10 +17927,12 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.3.0 + optional: true pgpass@1.0.5: dependencies: split2: 4.2.0 + optional: true picocolors@1.1.1: {} @@ -20663,43 +17944,22 @@ snapshots: pify@2.3.0: {} - pify@3.0.0: {} - - pinkie-promise@2.0.1: - dependencies: - pinkie: 2.0.4 - - pinkie@2.0.4: {} - pirates@4.0.7: {} - pkg-dir@2.0.0: - dependencies: - find-up: 2.1.0 - pkg-dir@4.2.0: dependencies: find-up: 4.1.0 + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + pluralize@8.0.0: {} - png-js@2.0.0: - dependencies: - fflate: 0.8.2 - pngjs@5.0.0: {} - popper.js@1.16.1: {} - - portfinder@1.0.38(supports-color@5.5.0): - dependencies: - async: 3.2.6 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - posix-character-classes@0.1.1: {} - possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.14): @@ -20740,15 +18000,19 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: {} + postgres-array@2.0.0: + optional: true - postgres-bytea@1.0.1: {} + postgres-bytea@1.0.1: + optional: true - postgres-date@1.0.7: {} + postgres-date@1.0.7: + optional: true postgres-interval@1.2.0: dependencies: xtend: 4.0.2 + optional: true prelude-ls@1.2.1: {} @@ -20760,15 +18024,14 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@5.22.0: + prisma@6.19.3(typescript@5.9.3): dependencies: - '@prisma/engines': 5.22.0 + '@prisma/config': 6.19.3 + '@prisma/engines': 6.19.3 optionalDependencies: - fsevents: 2.3.3 - - process-nextick-args@2.0.1: {} - - process@0.11.10: {} + typescript: 5.9.3 + transitivePeerDependencies: + - magicast prompts@2.4.2: dependencies: @@ -20781,107 +18044,13 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - prosemirror-changeset@2.4.1: - dependencies: - prosemirror-transform: 1.12.0 - - prosemirror-commands@1.7.1: - dependencies: - prosemirror-model: 1.25.4 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - prosemirror-dropcursor@1.8.2: - dependencies: - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 - - prosemirror-gapcursor@1.4.1: - dependencies: - prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.4 - prosemirror-state: 1.4.4 - prosemirror-view: 1.41.8 - - prosemirror-history@1.5.0: - dependencies: - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 - rope-sequence: 1.3.4 - - prosemirror-keymap@1.2.3: - dependencies: - prosemirror-state: 1.4.4 - w3c-keyname: 2.2.8 - - prosemirror-model@1.25.4: - dependencies: - orderedmap: 2.1.1 - - prosemirror-schema-list@1.5.1: - dependencies: - prosemirror-model: 1.25.4 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - prosemirror-state@1.4.4: - dependencies: - prosemirror-model: 1.25.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 - - prosemirror-tables@1.8.5: - dependencies: - prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.4 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 - - prosemirror-transform@1.12.0: - dependencies: - prosemirror-model: 1.25.4 - - prosemirror-view@1.41.8: - dependencies: - prosemirror-model: 1.25.4 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-compare@3.0.1: {} - proxy-from-env@2.1.0: {} - proxy-memoize@3.0.1: - dependencies: - proxy-compare: 3.0.1 - - prr@1.0.1: {} - - pseudomap@1.0.2: {} - - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - public-encrypt@4.0.3: - dependencies: - bn.js: 4.12.3 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - parse-asn1: 5.1.9 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - - punycode@1.4.1: {} - punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -20896,53 +18065,10 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.5.5: {} - - query-string@7.1.3: - dependencies: - decode-uri-component: 0.2.2 - filter-obj: 1.1.0 - split-on-first: 1.1.0 - strict-uri-encode: 2.0.0 - - querystring-es3@0.2.1: {} - - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} - queue@6.0.2: - dependencies: - inherits: 2.0.4 - - raf@3.4.1: - dependencies: - performance-now: 2.1.0 - optional: true - - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - - randomfill@1.0.4: - dependencies: - randombytes: 2.1.0 - safe-buffer: 5.2.1 - range-parser@1.2.1: {} - rapiq@0.9.0: - dependencies: - ebec: 1.1.1 - smob: 1.6.1 - - raw-body@2.5.3: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - raw-body@3.0.2: dependencies: bytes: 3.1.2 @@ -21205,7 +18331,6 @@ snapshots: loose-envify: 1.4.0 prop-types: 15.8.1 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) react@16.14.0: dependencies: @@ -21223,77 +18348,12 @@ snapshots: dependencies: pify: 2.3.0 - read-pkg-up@1.0.1: - dependencies: - find-up: 1.1.2 - read-pkg: 1.1.0 - - read-pkg-up@2.0.0: - dependencies: - find-up: 2.1.0 - read-pkg: 2.0.0 - - read-pkg@1.1.0: - dependencies: - load-json-file: 1.1.0 - normalize-package-data: 2.5.0 - path-type: 1.1.0 - - read-pkg@2.0.0: - dependencies: - load-json-file: 2.0.0 - normalize-package-data: 2.5.0 - path-type: 2.0.0 - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - - readable-web-to-node-stream@3.0.4: - dependencies: - readable-stream: 4.7.0 - - readdir-glob@1.1.3: - dependencies: - minimatch: 5.1.9 - - readdirp@2.2.1(supports-color@4.5.0): - dependencies: - graceful-fs: 4.2.11 - micromatch: 3.1.10(supports-color@4.5.0) - readable-stream: 2.3.8 - transitivePeerDependencies: - - supports-color - optional: true - - readdirp@2.2.1(supports-color@5.5.0): - dependencies: - graceful-fs: 4.2.11 - micromatch: 3.1.10(supports-color@5.5.0) - readable-stream: 2.3.8 - transitivePeerDependencies: - - supports-color - readdirp@3.6.0: dependencies: picomatch: 2.3.2 @@ -21346,13 +18406,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerator-runtime@0.13.11: {} - - regex-not@1.0.2: - dependencies: - extend-shallow: 3.0.2 - safe-regex: 1.1.0 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.9 @@ -21362,76 +18415,20 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - relative@3.0.2: - dependencies: - isobject: 2.1.0 - - remarkable@1.7.4: - dependencies: - argparse: 1.0.10 - autolinker: 0.28.1 - - remove-trailing-separator@1.1.0: {} - - repeat-element@1.1.4: {} - - repeat-string@1.6.1: {} - - repeating@2.0.1: - dependencies: - is-finite: 1.1.0 - - request@2.88.2: - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.5 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - require-directory@2.1.1: {} require-from-string@2.0.2: {} - require-main-filename@1.0.1: {} - require-main-filename@2.0.0: {} - requires-port@1.0.0: {} - - reselect@5.1.1: {} - - resolve-cwd@2.0.0: - dependencies: - resolve-from: 3.0.0 - resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 - resolve-from@3.0.0: {} - resolve-from@4.0.0: {} resolve-from@5.0.0: {} - resolve-url@0.2.1: {} - resolve.exports@2.0.3: {} resolve@1.22.12: @@ -21460,34 +18457,14 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - restructure@3.0.2: {} - - ret@0.1.15: {} - reusify@1.1.0: {} rfdc@1.4.1: {} - rgbcolor@1.0.1: - optional: true - - right-align@0.1.3: - dependencies: - align-text: 0.1.4 - - rimraf@2.7.1: - dependencies: - glob: 7.2.3 - rimraf@3.0.2: dependencies: glob: 7.2.3 - ripemd160@2.0.3: - dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - rollup@4.60.3: dependencies: '@types/estree': 1.0.8 @@ -21519,8 +18496,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 - rope-sequence@1.3.4: {} - router@2.2.0: dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -21555,8 +18530,6 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.1.2: {} - safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -21570,18 +18543,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safe-regex@1.1.0: - dependencies: - ret: 0.1.15 - safer-buffer@2.1.2: {} - sax@1.6.0: {} - - saxes@5.0.1: - dependencies: - xmlchars: 2.2.0 - saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -21612,42 +18575,10 @@ snapshots: ajv-formats: 2.1.1(ajv@8.20.0) ajv-keywords: 5.1.0(ajv@8.20.0) - scroll@3.0.1: {} - - scrollparent@2.1.0: {} - - select-hose@2.0.0: {} - - self-closing-tags@1.0.1: {} - - selfsigned@1.10.14: - dependencies: - node-forge: 0.10.0 - - semver@5.7.2: {} - semver@6.3.1: {} semver@7.8.0: {} - send@0.19.2(supports-color@5.5.0): - dependencies: - debug: 2.6.9(supports-color@5.5.0) - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.1 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - send@1.2.1: dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -21664,26 +18595,8 @@ snapshots: transitivePeerDependencies: - supports-color - serve-index@1.9.2(supports-color@5.5.0): - dependencies: - accepts: 1.3.8 - batch: 0.6.1 - debug: 2.6.9(supports-color@5.5.0) - escape-html: 1.0.3 - http-errors: 1.8.1 - mime-types: 2.1.35 - parseurl: 1.3.3 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.3(supports-color@5.5.0): - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.2(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color + seq-queue@0.0.5: + optional: true serve-static@2.2.1: dependencies: @@ -21696,8 +18609,6 @@ snapshots: set-blocking@2.0.0: {} - set-cookie-parser@2.7.2: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -21714,25 +18625,12 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - set-getter@0.1.1: - dependencies: - to-object-path: 0.3.0 - set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - - setimmediate@1.0.5: {} - setprototypeof@1.2.0: {} sha.js@2.4.12: @@ -21741,16 +18639,10 @@ snapshots: safe-buffer: 5.2.1 to-buffer: 1.2.2 - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} side-channel-list@1.0.1: @@ -21885,14 +18777,6 @@ snapshots: source-map-js@1.2.1: {} - source-map-resolve@0.5.3: - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - resolve-url: 0.2.1 - source-map-url: 0.4.1 - urix: 0.1.0 - source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -21903,78 +18787,18 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map-url@0.4.1: {} - - source-map@0.5.7: {} - source-map@0.6.1: {} source-map@0.7.4: {} - source-map@0.7.6: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 - - spdx-license-ids@3.0.23: {} - - spdy-transport@3.0.0(supports-color@5.5.0): - dependencies: - debug: 4.4.3(supports-color@5.5.0) - detect-node: 2.1.0 - hpack.js: 2.1.6 - obuf: 1.1.2 - readable-stream: 3.6.2 - wbuf: 1.7.3 - transitivePeerDependencies: - - supports-color - - spdy@4.0.2(supports-color@5.5.0): - dependencies: - debug: 4.4.3(supports-color@5.5.0) - handle-thing: 2.0.1 - http-deceiver: 1.2.7 - select-hose: 2.0.0 - spdy-transport: 3.0.0(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - split-on-first@1.1.0: {} - - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - split2@4.2.0: {} sprintf-js@1.0.3: {} sql-highlight@6.1.0: {} - ssf@0.11.2: - dependencies: - frac: 1.1.2 - - sshpk@1.18.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 + sqlstring@2.3.3: + optional: true stack-utils@2.0.6: dependencies: @@ -21982,16 +18806,6 @@ snapshots: stackback@0.0.2: {} - stackblur-canvas@2.7.0: - optional: true - - static-extend@0.1.2: - dependencies: - define-property: 0.2.5 - object-copy: 0.1.0 - - statuses@1.5.0: {} - statuses@2.0.2: {} std-env@3.10.0: {} @@ -22001,23 +18815,8 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - stream-browserify@2.0.2: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - - stream-http@2.8.3: - dependencies: - builtin-status-codes: 3.0.0 - inherits: 2.0.4 - readable-stream: 2.3.8 - to-arraybuffer: 1.0.1 - xtend: 4.0.2 - streamsearch@1.1.0: {} - strict-uri-encode@2.0.0: {} - string-argv@0.3.2: {} string-length@4.0.2: @@ -22025,17 +18824,6 @@ snapshots: char-regex: 1.0.2 strip-ansi: 6.0.1 - string-width@1.0.2: - dependencies: - code-point-at: 1.1.0 - is-fullwidth-code-point: 1.0.0 - strip-ansi: 3.0.1 - - string-width@2.1.1: - dependencies: - is-fullwidth-code-point: 2.0.0 - strip-ansi: 4.0.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -22098,22 +18886,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - strip-ansi@3.0.1: - dependencies: - ansi-regex: 2.1.1 - - strip-ansi@4.0.0: - dependencies: - ansi-regex: 3.0.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -22122,45 +18898,20 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-bom@2.0.0: - dependencies: - is-utf8: 0.2.1 - strip-bom@3.0.0: {} strip-bom@4.0.0: {} - strip-eof@1.0.0: {} - strip-final-newline@2.0.0: {} strip-final-newline@3.0.0: {} - strip-indent@1.0.1: - dependencies: - get-stdin: 4.0.1 - strip-json-comments@3.1.1: {} - striptags@3.2.0: {} - - strnum@1.1.2: {} - strtok3@10.3.5: dependencies: '@tokenizer/token': 0.3.0 - strtok3@7.1.1: - dependencies: - '@tokenizer/token': 0.3.0 - peek-readable: 5.4.2 - - style-object-to-css-string@1.1.3: {} - - stylis@4.2.0: {} - - success-symbol@0.1.0: {} - sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -22193,14 +18944,6 @@ snapshots: transitivePeerDependencies: - supports-color - supports-color@4.5.0: - dependencies: - has-flag: 2.0.0 - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -22211,11 +18954,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svg-arc-to-cubic-bezier@3.2.0: {} - - svg-pathdata@6.0.3: - optional: true - swagger-ui-dist@5.17.14: {} swagger-ui-dist@5.32.4: @@ -22231,18 +18969,6 @@ snapshots: symbol-tree@3.2.4: {} - tabbable@6.4.0: {} - - tailwind-merge@3.6.0: {} - - tailwind-scrollbar-hide@4.0.0(tailwindcss@4.3.0): - dependencies: - tailwindcss: 4.3.0 - - tailwindcss-animate@1.0.7(tailwindcss@4.3.0): - dependencies: - tailwindcss: 4.3.0 - tailwindcss@3.4.19(yaml@2.9.0): dependencies: '@alloc/quick-lru': 5.2.0 @@ -22271,20 +18997,8 @@ snapshots: - tsx - yaml - tailwindcss@4.3.0: {} - - tapable@0.2.9: {} - tapable@2.3.3: {} - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - tar@6.2.1: dependencies: chownr: 2.0.0 @@ -22309,22 +19023,6 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - tesseract.js-core@7.0.0: {} - - tesseract.js@7.0.0: - dependencies: - bmp-js: 0.1.0 - idb-keyval: 6.2.2 - is-url: 1.2.4 - node-fetch: 2.7.0 - opencollective-postinstall: 2.0.3 - regenerator-runtime: 0.13.11 - tesseract.js-core: 7.0.0 - wasm-feature-detect: 1.8.0 - zlibjs: 0.3.1 - transitivePeerDependencies: - - encoding - test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.6 @@ -22333,10 +19031,6 @@ snapshots: text-extensions@2.4.0: {} - text-segmentation@1.0.3: - dependencies: - utrie: 1.0.2 - text-table@0.2.0: {} thenify-all@1.6.0: @@ -22347,31 +19041,8 @@ snapshots: dependencies: any-promise: 1.3.0 - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - - through2@4.0.2: - dependencies: - readable-stream: 3.6.2 - through@2.3.8: {} - thunky@1.1.0: {} - - time-stamp@1.1.0: {} - - time-stamp@2.2.0: {} - - timers-browserify@2.0.12: - dependencies: - setimmediate: 1.0.5 - - tiny-inflate@1.0.3: {} - - tiny-invariant@1.3.3: {} - tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -22383,8 +19054,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinymce@7.9.2: {} - tinypool@1.1.1: {} tinyrainbow@1.2.0: {} @@ -22397,58 +19066,26 @@ snapshots: dependencies: tldts-core: 6.1.86 - tmp@0.2.5: {} - tmpl@1.0.5: {} - to-arraybuffer@1.0.1: {} - to-buffer@1.2.2: dependencies: isarray: 2.0.5 safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 - to-gfm-code-block@0.1.1: {} - - to-object-path@0.3.0: - dependencies: - kind-of: 3.2.2 - - to-regex-range@2.1.1: - dependencies: - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - to-regex@3.0.2: - dependencies: - define-property: 2.0.2 - extend-shallow: 3.0.2 - regex-not: 1.0.2 - safe-regex: 1.1.0 - toidentifier@1.0.1: {} - token-types@5.0.1: - dependencies: - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - token-types@6.1.2: dependencies: '@borewit/text-codec': 0.2.2 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - tough-cookie@2.5.0: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - tough-cookie@5.1.2: dependencies: tldts: 6.1.86 @@ -22459,22 +19096,6 @@ snapshots: dependencies: punycode: 2.3.1 - traverse@0.3.9: {} - - tree-changes@0.11.3: - dependencies: - '@gilbarbara/deep-equal': 0.3.1 - is-lite: 1.2.1 - - tree-changes@0.9.3: - dependencies: - '@gilbarbara/deep-equal': 0.1.2 - is-lite: 0.8.2 - - trim-canvas@0.1.2: {} - - trim-newlines@1.0.0: {} - ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -22501,16 +19122,6 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 29.7.0 - ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.0): - dependencies: - chalk: 4.1.2 - enhanced-resolve: 5.21.3 - micromatch: 4.0.8 - semver: 7.8.0 - source-map: 0.7.6 - typescript: 5.9.3 - webpack: 5.106.0 - ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -22551,12 +19162,6 @@ snapshots: tslib@2.8.1: {} - tty-browserify@0.0.0: {} - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - turbo@2.9.12: optionalDependencies: '@turbo/darwin-64': 2.9.12 @@ -22566,8 +19171,6 @@ snapshots: '@turbo/windows-64': 2.9.12 '@turbo/windows-arm64': 2.9.12 - tweetnacl@0.14.5: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -22591,8 +19194,6 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - type@2.7.3: {} - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -22628,24 +19229,7 @@ snapshots: typedarray@0.0.6: {} - typeof-article@0.1.1: - dependencies: - kind-of: 3.2.2 - - typeorm-extension@3.9.0(@faker-js/faker@10.4.0)(typeorm@0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))): - dependencies: - '@faker-js/faker': 10.4.0 - consola: 3.4.2 - envix: 1.5.0 - locter: 2.2.1 - pascal-case: 3.1.2 - rapiq: 0.9.0 - reflect-metadata: 0.2.2 - smob: 1.6.1 - typeorm: 0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) - yargs: 18.0.0 - - typeorm@0.3.29(babel-plugin-macros@3.1.0)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): + typeorm@0.3.29(babel-plugin-macros@3.1.0)(mysql2@3.15.3)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: '@sqltools/formatter': 1.2.5 ansis: 4.3.0 @@ -22663,6 +19247,7 @@ snapshots: uuid: 11.1.1 yargs: 17.7.2 optionalDependencies: + mysql2: 3.15.3 pg: 8.20.0 ts-node: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) transitivePeerDependencies: @@ -22671,26 +19256,9 @@ snapshots: typescript@5.9.3: {} - uglify-js@2.8.29: - dependencies: - source-map: 0.5.7 - yargs: 3.10.0 - optionalDependencies: - uglify-to-browserify: 1.0.2 - uglify-js@3.19.3: optional: true - uglify-to-browserify@1.0.2: - optional: true - - uglifyjs-webpack-plugin@0.4.6(webpack@3.12.0): - dependencies: - source-map: 0.5.7 - uglify-js: 2.8.29 - webpack: 3.12.0 - webpack-sources: 1.4.3 - uid@2.0.2: dependencies: '@lukeed/csprng': 1.1.0 @@ -22708,61 +19276,18 @@ snapshots: undici-types@7.16.0: {} - unicode-properties@1.4.1: - dependencies: - base64-js: 1.5.1 - unicode-trie: 2.0.0 - - unicode-trie@2.0.0: - dependencies: - pako: 0.2.9 - tiny-inflate: 1.0.3 - unicorn-magic@0.1.0: {} - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - - universal-cookie@8.1.2: - dependencies: - cookie: 1.1.1 - universalify@2.0.1: {} unpipe@1.0.0: {} - unset-value@1.0.0: - dependencies: - has-value: 0.3.1 - isobject: 3.0.1 - - unzipper@0.10.14: - dependencies: - big-integer: 1.6.52 - binary: 0.3.0 - bluebird: 3.4.7 - buffer-indexof-polyfill: 1.0.2 - duplexer2: 0.1.4 - fstream: 1.0.12 - graceful-fs: 4.2.11 - listenercount: 1.0.1 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - - upath@1.2.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 - uqr@0.1.2: {} - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -22826,34 +19351,10 @@ snapshots: util-deprecate@1.0.2: {} - util@0.10.4: - dependencies: - inherits: 2.0.3 - - util@0.11.1: - dependencies: - inherits: 2.0.3 - - util@0.12.5: - dependencies: - inherits: 2.0.4 - is-arguments: 1.2.0 - is-generator-function: 1.1.2 - is-typed-array: 1.1.15 - which-typed-array: 1.1.20 - utils-merge@1.0.1: {} - utrie@1.0.2: - dependencies: - base64-arraybuffer: 1.0.2 - uuid@11.1.1: {} - uuid@3.4.0: {} - - uuid@8.3.2: {} - v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -22862,11 +19363,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - validator@13.15.35: {} vary@1.1.2: {} @@ -22974,12 +19470,6 @@ snapshots: - supports-color - terser - vm-browserify@1.1.2: {} - - void-elements@3.1.0: {} - - w3c-keyname@2.2.8: {} - w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -22988,40 +19478,11 @@ snapshots: dependencies: makeerror: 1.0.12 - warning-symbol@0.1.0: {} - - warning@4.0.3: - dependencies: - loose-envify: 1.4.0 - - wasm-feature-detect@1.8.0: {} - - watchpack-chokidar2@2.0.1(supports-color@4.5.0): - dependencies: - chokidar: 2.1.8(supports-color@4.5.0) - transitivePeerDependencies: - - supports-color - optional: true - - watchpack@1.7.5(supports-color@4.5.0): - dependencies: - graceful-fs: 4.2.11 - neo-async: 2.6.2 - optionalDependencies: - chokidar: 3.6.0 - watchpack-chokidar2: 2.0.1(supports-color@4.5.0) - transitivePeerDependencies: - - supports-color - watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - wbuf@1.7.3: - dependencies: - minimalistic-assert: 1.0.1 - wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -23038,80 +19499,10 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@1.12.2(webpack@3.12.0): - dependencies: - memory-fs: 0.4.1 - mime: 1.6.0 - path-is-absolute: 1.0.1 - range-parser: 1.2.1 - time-stamp: 2.2.0 - webpack: 3.12.0 - - webpack-dev-server@2.11.5(webpack@3.12.0): - dependencies: - ansi-html: 0.0.7 - array-includes: 3.1.9 - bonjour: 3.5.1 - chokidar: 2.1.8(supports-color@5.5.0) - compression: 1.8.1(supports-color@5.5.0) - connect-history-api-fallback: 1.6.0 - debug: 3.2.7(supports-color@5.5.0) - del: 3.0.0 - express: 4.22.2(supports-color@5.5.0) - html-entities: 1.4.0 - http-proxy-middleware: 0.19.2(debug@3.2.7(supports-color@5.5.0))(supports-color@5.5.0) - import-local: 1.0.0 - internal-ip: 1.2.0 - ip: 1.1.9 - killable: 1.0.1 - loglevel: 1.9.2 - opn: 5.5.0 - portfinder: 1.0.38(supports-color@5.5.0) - selfsigned: 1.10.14 - serve-index: 1.9.2(supports-color@5.5.0) - sockjs: 0.3.19 - sockjs-client: 1.1.5(supports-color@5.5.0) - spdy: 4.0.2(supports-color@5.5.0) - strip-ansi: 3.0.1 - supports-color: 5.5.0 - webpack: 3.12.0 - webpack-dev-middleware: 1.12.2(webpack@3.12.0) - yargs: 6.6.0 - webpack-node-externals@3.0.0: {} - webpack-sources@1.4.3: - dependencies: - source-list-map: 2.0.1 - source-map: 0.6.1 - webpack-sources@3.4.1: {} - webpack@3.12.0: - dependencies: - acorn: 5.7.4 - acorn-dynamic-import: 2.0.2 - ajv: 6.15.0 - ajv-keywords: 3.5.2(ajv@6.15.0) - async: 2.6.4 - enhanced-resolve: 3.4.1 - escope: 3.6.0 - interpret: 1.4.0 - json-loader: 0.5.7 - json5: 0.5.1 - loader-runner: 2.4.0 - loader-utils: 1.4.2 - memory-fs: 0.4.1 - mkdirp: 0.5.6 - node-libs-browser: 2.2.1 - source-map: 0.5.7 - supports-color: 4.5.0 - tapable: 0.2.9 - uglifyjs-webpack-plugin: 0.4.6(webpack@3.12.0) - watchpack: 1.7.5(supports-color@4.5.0) - webpack-sources: 1.4.3 - yargs: 8.0.2 - webpack@5.106.0: dependencies: '@types/eslint-scope': 3.7.7 @@ -23153,14 +19544,6 @@ snapshots: - postcss - uglify-js - websocket-driver@0.7.4: - dependencies: - http-parser-js: 0.5.10 - safe-buffer: 5.2.1 - websocket-extensions: 0.1.4 - - websocket-extensions@0.1.4: {} - whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -23208,8 +19591,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-module@1.0.0: {} - which-module@2.0.1: {} which-typed-array@1.1.20: @@ -23222,10 +19603,6 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -23239,23 +19616,10 @@ snapshots: dependencies: string-width: 4.2.3 - window-size@0.1.0: {} - - wmf@1.0.2: {} - word-wrap@1.2.5: {} - word@0.3.0: {} - - wordwrap@0.0.2: {} - wordwrap@1.0.0: {} - wrap-ansi@2.1.0: - dependencies: - string-width: 1.0.2 - strip-ansi: 3.0.1 - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -23287,50 +19651,25 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@8.18.3: {} - ws@8.20.0: {} - xlsx@0.18.5: - dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 - xml-name-validator@5.0.0: {} - xml2js@0.5.0: - dependencies: - sax: 1.6.0 - xmlbuilder: 11.0.1 - - xml@1.0.1: {} - - xmlbuilder@11.0.1: {} - xmlchars@2.2.0: {} - xmlhttprequest-ssl@2.1.2: {} - - xtend@4.0.2: {} - - y18n@3.2.2: {} + xtend@4.0.2: + optional: true y18n@4.0.3: {} y18n@5.0.8: {} - yallist@2.1.2: {} - yallist@3.1.1: {} yallist@4.0.0: {} - yaml@1.10.3: {} + yaml@1.10.3: + optional: true yaml@2.9.0: {} @@ -23341,16 +19680,6 @@ snapshots: yargs-parser@21.1.1: {} - yargs-parser@22.0.0: {} - - yargs-parser@4.2.1: - dependencies: - camelcase: 3.0.0 - - yargs-parser@7.0.0: - dependencies: - camelcase: 4.1.0 - yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -23375,56 +19704,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yargs@18.0.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 7.2.0 - y18n: 5.0.8 - yargs-parser: 22.0.0 - - yargs@3.10.0: - dependencies: - camelcase: 1.2.1 - cliui: 2.1.0 - decamelize: 1.2.0 - window-size: 0.1.0 - - yargs@6.6.0: - dependencies: - camelcase: 3.0.0 - cliui: 3.2.0 - decamelize: 1.2.0 - get-caller-file: 1.0.3 - os-locale: 1.4.0 - read-pkg-up: 1.0.1 - require-directory: 2.1.1 - require-main-filename: 1.0.1 - set-blocking: 2.0.0 - string-width: 1.0.2 - which-module: 1.0.0 - y18n: 3.2.2 - yargs-parser: 4.2.1 - - yargs@8.0.2: - dependencies: - camelcase: 4.1.0 - cliui: 3.2.0 - decamelize: 1.2.0 - get-caller-file: 1.0.3 - os-locale: 2.1.0 - read-pkg-up: 2.0.0 - require-directory: 2.1.1 - require-main-filename: 1.0.1 - set-blocking: 2.0.0 - string-width: 2.1.1 - which-module: 2.0.1 - y18n: 3.2.2 - yargs-parser: 7.0.0 - - year@0.2.1: {} - yn@3.1.1: {} yocto-queue@0.1.0: {} @@ -23433,18 +19712,6 @@ snapshots: yoctocolors-cjs@2.1.3: {} - yoga-layout@3.2.1: {} - - zip-stream@4.1.1: - dependencies: - archiver-utils: 3.0.4 - compress-commons: 4.1.2 - readable-stream: 3.6.2 - - zlibjs@0.3.1: {} - - zod@3.25.76: {} - zustand@5.0.13(@types/react@18.3.28)(immer@11.1.8)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.28