From d3bf8080b6fda241d2425efef4996eeb00e2f9d8 Mon Sep 17 00:00:00 2001 From: Michilis Date: Thu, 27 Nov 2025 22:13:37 +0000 Subject: [PATCH] Initial commit: Lightning Lottery - Bitcoin Lightning Network powered lottery Features: - Lightning Network payments via LNbits integration - Provably fair draws using CSPRNG - Random ticket number generation - Automatic payouts with retry/redraw logic - Nostr authentication (NIP-07) - Multiple draw cycles (hourly, daily, weekly, monthly) - PostgreSQL and SQLite database support - Real-time countdown and payment animations - Swagger API documentation - Docker support Stack: - Backend: Node.js, TypeScript, Express - Frontend: Next.js, React, TailwindCSS, Redux - Payments: LNbits --- .gitignore | 90 + README.md | 318 + back_end/.gitignore | 11 + back_end/Dockerfile | 24 + back_end/README.md | 142 + back_end/env.example | 84 + back_end/package-lock.json | 3115 ++++++++ back_end/package.json | 47 + back_end/src/app.ts | 100 + back_end/src/config/index.ts | 159 + back_end/src/config/swagger.ts | 118 + back_end/src/controllers/admin.ts | 191 + back_end/src/controllers/public.ts | 483 ++ back_end/src/controllers/user.ts | 348 + back_end/src/controllers/webhooks.ts | 98 + back_end/src/database/index.ts | 334 + back_end/src/database/schema.sql | 131 + back_end/src/index.ts | 63 + back_end/src/middleware/auth.ts | 86 + back_end/src/middleware/rateLimit.ts | 61 + back_end/src/routes/admin.ts | 130 + back_end/src/routes/public.ts | 191 + back_end/src/routes/user.ts | 152 + back_end/src/routes/webhooks.ts | 44 + back_end/src/scheduler/index.ts | 199 + back_end/src/services/draw.ts | 443 ++ back_end/src/services/lnbits.ts | 156 + back_end/src/services/paymentMonitor.ts | 130 + back_end/src/services/paymentProcessor.ts | 129 + back_end/src/services/payout.ts | 176 + back_end/src/types/index.ts | 108 + back_end/src/utils/validation.ts | 70 + back_end/tsconfig.json | 19 + docker-compose.yml | 75 + front_end/.gitignore | 37 + front_end/Dockerfile | 31 + front_end/README.md | 106 + front_end/env.example | 40 + front_end/next.config.js | 12 + front_end/package-lock.json | 6833 +++++++++++++++++ front_end/package.json | 32 + front_end/postcss.config.js | 7 + front_end/src/app/about/page.tsx | 190 + front_end/src/app/buy/page.tsx | 367 + front_end/src/app/dashboard/page.tsx | 159 + front_end/src/app/dashboard/tickets/page.tsx | 119 + front_end/src/app/dashboard/wins/page.tsx | 111 + front_end/src/app/globals.css | 53 + front_end/src/app/layout.tsx | 36 + front_end/src/app/page.tsx | 293 + front_end/src/app/past-wins/page.tsx | 117 + front_end/src/app/providers.tsx | 9 + front_end/src/app/tickets/[id]/page.tsx | 192 + front_end/src/components/DrawAnimation.tsx | 214 + front_end/src/components/Footer.tsx | 29 + front_end/src/components/JackpotCountdown.tsx | 58 + .../src/components/JackpotPotDisplay.tsx | 20 + .../src/components/LightningInvoiceCard.tsx | 154 + front_end/src/components/LoadingSpinner.tsx | 11 + front_end/src/components/NostrLoginButton.tsx | 72 + front_end/src/components/PayoutStatus.tsx | 55 + front_end/src/components/TicketList.tsx | 40 + front_end/src/components/TopBar.tsx | 62 + front_end/src/config/index.ts | 7 + front_end/src/constants/strings.ts | 96 + front_end/src/lib/api.ts | 224 + front_end/src/lib/format.ts | 79 + front_end/src/lib/nostr.ts | 100 + front_end/src/store/hooks.ts | 6 + front_end/src/store/index.ts | 16 + front_end/src/store/jackpotSlice.ts | 45 + front_end/src/store/purchaseSlice.ts | 45 + front_end/src/store/userSlice.ts | 32 + front_end/tailwind.config.js | 22 + front_end/tsconfig.json | 28 + 75 files changed, 18184 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 back_end/.gitignore create mode 100644 back_end/Dockerfile create mode 100644 back_end/README.md create mode 100644 back_end/env.example create mode 100644 back_end/package-lock.json create mode 100644 back_end/package.json create mode 100644 back_end/src/app.ts create mode 100644 back_end/src/config/index.ts create mode 100644 back_end/src/config/swagger.ts create mode 100644 back_end/src/controllers/admin.ts create mode 100644 back_end/src/controllers/public.ts create mode 100644 back_end/src/controllers/user.ts create mode 100644 back_end/src/controllers/webhooks.ts create mode 100644 back_end/src/database/index.ts create mode 100644 back_end/src/database/schema.sql create mode 100644 back_end/src/index.ts create mode 100644 back_end/src/middleware/auth.ts create mode 100644 back_end/src/middleware/rateLimit.ts create mode 100644 back_end/src/routes/admin.ts create mode 100644 back_end/src/routes/public.ts create mode 100644 back_end/src/routes/user.ts create mode 100644 back_end/src/routes/webhooks.ts create mode 100644 back_end/src/scheduler/index.ts create mode 100644 back_end/src/services/draw.ts create mode 100644 back_end/src/services/lnbits.ts create mode 100644 back_end/src/services/paymentMonitor.ts create mode 100644 back_end/src/services/paymentProcessor.ts create mode 100644 back_end/src/services/payout.ts create mode 100644 back_end/src/types/index.ts create mode 100644 back_end/src/utils/validation.ts create mode 100644 back_end/tsconfig.json create mode 100644 docker-compose.yml create mode 100644 front_end/.gitignore create mode 100644 front_end/Dockerfile create mode 100644 front_end/README.md create mode 100644 front_end/env.example create mode 100644 front_end/next.config.js create mode 100644 front_end/package-lock.json create mode 100644 front_end/package.json create mode 100644 front_end/postcss.config.js create mode 100644 front_end/src/app/about/page.tsx create mode 100644 front_end/src/app/buy/page.tsx create mode 100644 front_end/src/app/dashboard/page.tsx create mode 100644 front_end/src/app/dashboard/tickets/page.tsx create mode 100644 front_end/src/app/dashboard/wins/page.tsx create mode 100644 front_end/src/app/globals.css create mode 100644 front_end/src/app/layout.tsx create mode 100644 front_end/src/app/page.tsx create mode 100644 front_end/src/app/past-wins/page.tsx create mode 100644 front_end/src/app/providers.tsx create mode 100644 front_end/src/app/tickets/[id]/page.tsx create mode 100644 front_end/src/components/DrawAnimation.tsx create mode 100644 front_end/src/components/Footer.tsx create mode 100644 front_end/src/components/JackpotCountdown.tsx create mode 100644 front_end/src/components/JackpotPotDisplay.tsx create mode 100644 front_end/src/components/LightningInvoiceCard.tsx create mode 100644 front_end/src/components/LoadingSpinner.tsx create mode 100644 front_end/src/components/NostrLoginButton.tsx create mode 100644 front_end/src/components/PayoutStatus.tsx create mode 100644 front_end/src/components/TicketList.tsx create mode 100644 front_end/src/components/TopBar.tsx create mode 100644 front_end/src/config/index.ts create mode 100644 front_end/src/constants/strings.ts create mode 100644 front_end/src/lib/api.ts create mode 100644 front_end/src/lib/format.ts create mode 100644 front_end/src/lib/nostr.ts create mode 100644 front_end/src/store/hooks.ts create mode 100644 front_end/src/store/index.ts create mode 100644 front_end/src/store/jackpotSlice.ts create mode 100644 front_end/src/store/purchaseSlice.ts create mode 100644 front_end/src/store/userSlice.ts create mode 100644 front_end/tailwind.config.js create mode 100644 front_end/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b9ad50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +# Dependencies +node_modules/ +.pnp/ +.pnp.js + +# Build outputs +dist/ +build/ +.next/ +out/ + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +*.env + +# Database files +*.db +*.db-shm +*.db-wal +*.sqlite +*.sqlite3 +back_end/data/ + +# App info / documentation (internal) +App_info/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Testing +coverage/ +.nyc_output/ + +# TypeScript +*.tsbuildinfo + +# Debug +.npm +.eslintcache + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Yarn +.yarn-integrity +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz + +# Temporary files +tmp/ +temp/ +*.tmp +*.temp + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db + +# Secrets and keys (extra safety) +*.pem +*.key +secrets/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd748b1 --- /dev/null +++ b/README.md @@ -0,0 +1,318 @@ +# ⚡ Lightning Lottery + +A complete Bitcoin Lightning Network powered lottery system with instant payouts to Lightning Addresses. + +## Features + +- ⚡ **Lightning Fast**: Instant ticket purchases and payouts via Lightning Network +- 🎲 **Provably Fair**: Cryptographically secure random number generation (CSPRNG) +- 🔐 **Secure**: Transaction-based ticket issuance, idempotent operations +- 🌐 **Anonymous or Nostr**: Buy tickets anonymously or login with Nostr +- 📱 **Mobile First**: Beautiful responsive design optimized for all devices +- 🏆 **Automatic Payouts**: Winners get paid automatically to their Lightning Address +- ⏰ **Multiple Cycles**: Support for hourly, daily, weekly, and monthly draws +- 🔄 **Auto-Redraw**: If payout fails, automatically selects a new winner +- 🎰 **Random Tickets**: Ticket numbers are randomly generated, not sequential +- 📊 **Swagger Docs**: Full API documentation at `/api-docs` + +## Architecture + +The system consists of three main components: + +1. **Backend API** (Node.js + TypeScript + Express) +2. **Frontend** (Next.js + React + TypeScript + TailwindCSS) +3. **Database** (PostgreSQL or SQLite) + +### Backend + +- RESTful API with comprehensive endpoints +- LNbits integration for Lightning payments +- Automated scheduler for draws and cycle generation +- JWT authentication for Nostr users +- Admin API for manual operations +- Payment monitoring with polling fallback +- Webhook support for instant payment confirmation + +### Frontend + +- Server-side rendered Next.js application +- Redux state management +- Real-time countdown timers +- Invoice QR code display with payment animations +- Automatic status polling +- Nostr NIP-07 authentication +- Draw animation with winner reveal +- Past winners display + +## Quick Start + +### Using Docker Compose (Recommended) + +1. Clone the repository: +```bash +git clone +cd LightningLotto +``` + +2. Configure environment: +```bash +cp back_end/env.example back_end/.env +cp front_end/env.example front_end/.env.local +# Edit both files with your configuration +``` + +3. Start services: +```bash +docker-compose up -d +``` + +4. Access the application: +- Frontend: http://localhost:3001 +- Backend API: http://localhost:3000 +- API Docs: http://localhost:3000/api-docs +- Health check: http://localhost:3000/health + +### Manual Setup + +#### Backend + +```bash +cd back_end +npm install +cp env.example .env +# Edit .env with your configuration + +# For PostgreSQL: +createdb lightning_lotto +psql lightning_lotto < src/database/schema.sql + +# For SQLite (default, no setup needed): +# Database file created automatically at data/lightning_lotto.db + +# Run development server +npm run dev + +# Or build and run production +npm run build +npm start +``` + +#### Frontend + +```bash +cd front_end +npm install +cp env.example .env.local +# Edit .env.local + +# Run development server +npm run dev + +# Or build and run production +npm run build +npm start +``` + +## Configuration + +### Required Environment Variables + +#### Backend (.env) + +```bash +# Database (PostgreSQL or SQLite) +DATABASE_URL=postgresql://user:pass@localhost:5432/lightning_lotto +# Or for SQLite (leave DATABASE_URL empty or use): +USE_SQLITE=true + +# LNbits Configuration +LNBITS_API_BASE_URL=https://your-lnbits-instance.com +LNBITS_ADMIN_KEY=your-lnbits-admin-key +LNBITS_WEBHOOK_SECRET=your-webhook-secret + +# Security +JWT_SECRET=your-jwt-secret-min-32-chars +ADMIN_API_KEY=your-admin-api-key + +# Lottery Settings +DEFAULT_TICKET_PRICE_SATS=1000 +DEFAULT_HOUSE_FEE_PERCENT=5 +PAYOUT_MAX_ATTEMPTS_BEFORE_REDRAW=2 +``` + +#### Frontend (.env.local) + +```bash +NEXT_PUBLIC_API_BASE_URL=http://localhost:3000 +``` + +See `back_end/env.example` and `front_end/env.example` for all configuration options. + +## API Endpoints + +### Public Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/jackpot/next` | Get next upcoming draw | +| POST | `/jackpot/buy` | Purchase tickets | +| GET | `/jackpot/past-wins` | List past winners | +| GET | `/tickets/:id` | Check ticket status | + +### User Endpoints (Nostr Auth) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/auth/nostr` | Authenticate with Nostr | +| GET | `/me` | Get user profile | +| PATCH | `/me/lightning-address` | Update Lightning address | +| GET | `/me/tickets` | User's ticket history | +| GET | `/me/wins` | User's win history | + +### Admin Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/admin/cycles` | List all cycles | +| POST | `/admin/cycles/:id/run-draw` | Manually trigger draw | +| GET | `/admin/payouts` | List payouts | +| POST | `/admin/payouts/:id/retry` | Retry failed payout | + +### Webhooks + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/webhooks/lnbits/payment` | LNbits payment callback | + +Full API documentation available at `/api-docs` (Swagger UI). + +## Database Schema + +7 main tables: + +- `lotteries` - Lottery configuration +- `jackpot_cycles` - Draw cycles with status and winners +- `ticket_purchases` - Purchase records with Lightning invoice info +- `tickets` - Individual tickets with random serial numbers +- `payouts` - Winner payouts with retry logic +- `users` - Nostr user accounts (optional) +- `draw_logs` - Audit trail for transparency + +## How It Works + +1. **Cycle Generation**: Scheduler automatically creates future draw cycles +2. **Ticket Purchase**: Users buy tickets, receive Lightning invoice +3. **Payment Processing**: LNbits webhook or polling confirms payment +4. **Ticket Issuance**: Random ticket numbers assigned in database transaction +5. **Draw Execution**: At scheduled time, winner selected using CSPRNG +6. **Payout**: Winner's Lightning Address paid automatically +7. **Retry/Redraw**: Failed payouts retried; new winner drawn after max attempts + +## Security Features + +- JWT tokens for user authentication +- Admin API key protection +- Webhook signature verification +- Rate limiting on all endpoints +- Idempotent payment processing +- Database transactions for atomic operations +- `crypto.randomBytes()` for winner selection +- `crypto.randomInt()` for ticket number generation +- No floating-point math (BIGINT for all sats) + +## Frontend Pages + +| Page | Description | +|------|-------------| +| `/` | Home page with jackpot countdown and winner display | +| `/buy` | Purchase tickets with Lightning invoice | +| `/tickets/:id` | View ticket status and draw results | +| `/past-wins` | Public list of past winners | +| `/about` | About page with how it works | +| `/dashboard` | User dashboard (Nostr login required) | +| `/dashboard/tickets` | User's ticket history | +| `/dashboard/wins` | User's win history | + +## Testing + +### Backend Tests +```bash +cd back_end +npm test +``` + +### Frontend Tests +```bash +cd front_end +npm test +``` + +## Deployment + +### Production Considerations + +1. Use strong secrets for `JWT_SECRET` and `ADMIN_API_KEY` +2. Configure proper CORS origins in backend +3. Use SSL/TLS (HTTPS) for all connections +4. Set up monitoring and logging +5. Configure database backups +6. Set up proper firewall rules +7. Consider using a CDN for frontend +8. Use PostgreSQL for production (SQLite for development) + +### Scaling + +- Backend is stateless and can be horizontally scaled +- Use connection pooling for database +- Consider Redis for caching +- Use message queue for high-volume webhooks + +## Monitoring + +Health check endpoint: +```bash +curl http://localhost:3000/health +``` + +Returns database and LNbits connectivity status. + +## Project Structure + +``` +LightningLotto/ +├── back_end/ +│ ├── src/ +│ │ ├── config/ # Configuration and Swagger setup +│ │ ├── controllers/ # Route handlers +│ │ ├── database/ # Database wrapper and schema +│ │ ├── middleware/ # Auth and rate limiting +│ │ ├── routes/ # API routes +│ │ ├── scheduler/ # Automated jobs +│ │ ├── services/ # Business logic +│ │ ├── types/ # TypeScript types +│ │ └── utils/ # Validation helpers +│ └── data/ # SQLite database (if used) +├── front_end/ +│ └── src/ +│ ├── app/ # Next.js pages +│ ├── components/ # React components +│ ├── config/ # Frontend config +│ ├── constants/ # Text strings +│ ├── lib/ # API client and utilities +│ └── store/ # Redux state +└── docker-compose.yml +``` + +## License + +MIT License - see LICENSE file for details + +## Acknowledgments + +- Built with [LNbits](https://lnbits.com/) for Lightning Network integration +- Uses [Nostr](https://nostr.com/) for decentralized authentication +- Inspired by the Bitcoin Lightning Network community + +--- + +⚡ **Win Bitcoin on the Lightning Network!** ⚡ diff --git a/back_end/.gitignore b/back_end/.gitignore new file mode 100644 index 0000000..19b9f84 --- /dev/null +++ b/back_end/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ +.env +*.log +.DS_Store +coverage/ + + +# SQLite database +data/ +*.db diff --git a/back_end/Dockerfile b/back_end/Dockerfile new file mode 100644 index 0000000..5cfb0a9 --- /dev/null +++ b/back_end/Dockerfile @@ -0,0 +1,24 @@ +FROM node:18-alpine + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci --only=production + +# Copy source +COPY . . + +# Build TypeScript +RUN npm run build + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +# Start server +CMD ["node", "dist/index.js"] + diff --git a/back_end/README.md b/back_end/README.md new file mode 100644 index 0000000..8f17a70 --- /dev/null +++ b/back_end/README.md @@ -0,0 +1,142 @@ +# Lightning Lottery Backend API + +Bitcoin Lightning Network powered lottery system with automatic payouts to Lightning Addresses. + +## Features + +- **Public Ticket Purchasing**: Anonymous ticket purchases via Lightning invoices +- **Automated Draws**: Scheduled draws using cryptographically secure RNG +- **Instant Payouts**: Automatic payouts to Lightning Addresses +- **Nostr Authentication**: Optional user accounts via Nostr signatures +- **Multiple Cycle Types**: Hourly, daily, weekly, and monthly jackpots +- **Admin Interface**: Manual draw triggers and payout management + +## Tech Stack + +- **Runtime**: Node.js with TypeScript +- **Framework**: Express.js +- **Database**: PostgreSQL +- **Lightning**: LNbits integration +- **Scheduler**: node-cron + +## Setup + +### Prerequisites + +- Node.js 18+ +- PostgreSQL 14+ +- LNbits instance + +### Installation + +1. Install dependencies: +```bash +npm install +``` + +2. Configure environment variables: +```bash +cp .env.example .env +# Edit .env with your configuration +``` + +3. Setup database: +```bash +# Create database +createdb lightning_lotto + +# Run schema +psql lightning_lotto < src/database/schema.sql +``` + +### Development + +```bash +npm run dev +``` + +### Production + +```bash +npm run build +npm start +``` + +### Scheduler (Optional Separate Process) + +```bash +npm run scheduler +``` + +## API Endpoints + +### Public Endpoints + +- `GET /jackpot/next` - Get next upcoming cycle +- `POST /jackpot/buy` - Create ticket purchase +- `GET /tickets/:id` - Get ticket status + +### User Endpoints (Nostr Auth) + +- `POST /auth/nostr` - Authenticate with Nostr +- `GET /me` - Get user profile +- `PATCH /me/lightning-address` - Update Lightning Address +- `GET /me/tickets` - Get user's tickets +- `GET /me/wins` - Get user's wins + +### Admin Endpoints + +- `GET /admin/cycles` - List all cycles +- `POST /admin/cycles/:id/run-draw` - Manually run draw +- `GET /admin/payouts` - List payouts +- `POST /admin/payouts/:id/retry` - Retry failed payout + +### Webhook Endpoints + +- `POST /webhooks/lnbits/payment` - LNbits payment notification + +## Configuration + +See `.env.example` for all configuration options. + +### Required Environment Variables + +- `DATABASE_URL` - PostgreSQL connection string +- `LNBITS_API_BASE_URL` - LNbits API URL +- `LNBITS_ADMIN_KEY` - LNbits admin/invoice key +- `JWT_SECRET` - Secret for JWT signing +- `ADMIN_API_KEY` - Admin endpoint authentication + +## Database Schema + +The system uses 7 main tables: + +- `lotteries` - Lottery configuration +- `jackpot_cycles` - Draw cycles +- `ticket_purchases` - Purchase records +- `tickets` - Individual tickets +- `payouts` - Winner payouts +- `users` - Nostr user accounts +- `draw_logs` - Audit trail + +## Security + +- JWT tokens for user authentication +- Admin API key for admin endpoints +- Webhook signature verification +- Rate limiting on all endpoints +- Idempotent payment processing +- Transaction-based ticket issuance + +## Scheduler Tasks + +The scheduler runs three main tasks: + +1. **Cycle Generator**: Creates future cycles (every 5 minutes) +2. **Draw Executor**: Triggers draws at scheduled times (every minute) +3. **Payout Retry**: Retries failed payouts with exponential backoff (every 10 minutes) + +## License + +MIT + diff --git a/back_end/env.example b/back_end/env.example new file mode 100644 index 0000000..6a2ea73 --- /dev/null +++ b/back_end/env.example @@ -0,0 +1,84 @@ +# Lightning Lottery Backend - Environment Configuration +# Copy this file to .env and fill in your values + +# ====================== +# Server Configuration +# ====================== +PORT=3000 +APP_BASE_URL=http://localhost:3000 +FRONTEND_BASE_URL=http://localhost:3001 +# Optional: comma-separated list to override allowed origins +# CORS_ALLOWED_ORIGINS=http://localhost:3001,https://app.yourdomain.com +NODE_ENV=development + +# ====================== +# Database Configuration +# ====================== +# Database type: "postgres" or "sqlite" +DATABASE_TYPE=sqlite + +# Database connection +# For PostgreSQL: postgresql://username:password@host:port/database +# For SQLite: ./data/lightning_lotto.db (relative path) +DATABASE_URL=./data/lightning_lotto.db + +# PostgreSQL example: +# DATABASE_TYPE=postgres +# DATABASE_URL=postgresql://user:password@localhost:5432/lightning_lotto + +# ====================== +# LNbits Configuration +# ====================== +# Your LNbits instance URL +LNBITS_API_BASE_URL=https://legend.lnbits.com + +# LNbits admin/invoice key (required for creating invoices and payouts) +# Get this from your LNbits wallet settings +LNBITS_ADMIN_KEY=your_lnbits_admin_key_here + +# Webhook secret for validating LNbits payment notifications +# Choose a random secret string +LNBITS_WEBHOOK_SECRET=your_webhook_secret_here + +# ====================== +# Security Configuration +# ====================== +# Secret for signing JWT tokens (use a strong random string) +# Generate with: openssl rand -hex 32 +JWT_SECRET=your_jwt_secret_here_change_in_production + +# Admin API key for admin endpoints +# Generate with: openssl rand -hex 32 +ADMIN_API_KEY=your_admin_api_key_here_change_in_production + +# ====================== +# Scheduler Configuration +# ====================== +# How often to check for draws that need to be executed (in seconds) +DRAW_SCHEDULER_INTERVAL_SECONDS=60 + +# How often to generate future cycles (in seconds) +CYCLE_GENERATOR_INTERVAL_SECONDS=300 + +# ====================== +# Lottery Configuration +# ====================== +# Default ticket price in satoshis +DEFAULT_TICKET_PRICE_SATS=1000 + +# House fee percentage (0-100) +# Example: 5 means 5% fee, winner gets 95% of pot +DEFAULT_HOUSE_FEE_PERCENT=5 + +# Maximum Lightning payout attempts before drawing a new winner +PAYOUT_MAX_ATTEMPTS=2 + +# ====================== +# Notes +# ====================== +# - SQLite is perfect for development and testing (no setup required!) +# - Use PostgreSQL for production deployments +# - Never commit the actual .env file to version control +# - Use strong, unique secrets in production +# - Keep your LNbits keys secure +# - Test with small amounts first diff --git a/back_end/package-lock.json b/back_end/package-lock.json new file mode 100644 index 0000000..b04f1b3 --- /dev/null +++ b/back_end/package-lock.json @@ -0,0 +1,3115 @@ +{ + "name": "lightning-lotto-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lightning-lotto-backend", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "@types/ws": "^8.18.1", + "axios": "^1.6.2", + "better-sqlite3": "^12.4.6", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "jsonwebtoken": "^9.0.2", + "node-cron": "^3.0.2", + "pg": "^8.11.3", + "socket.io": "^4.8.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.10.5", + "@types/node-cron": "^3.0.11", + "@types/pg": "^8.10.9", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.3" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", + "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "license": "MIT" + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/better-sqlite3": { + "version": "12.4.6", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.6.tgz", + "integrity": "sha512-gaYt9yqTbQ1iOxLpJA8FPR5PiaHP+jlg8I5EX0Rs2KFwNzhBsF40KzMZS5FwelY7RG0wzaucWdqSAJM3uNCPCg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "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 + } + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "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 + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", + "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "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 + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validator": { + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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 + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + } + } +} diff --git a/back_end/package.json b/back_end/package.json new file mode 100644 index 0000000..2a55b98 --- /dev/null +++ b/back_end/package.json @@ -0,0 +1,47 @@ +{ + "name": "lightning-lotto-backend", + "version": "1.0.0", + "description": "Lightning Lottery Backend API", + "main": "dist/index.js", + "scripts": { + "dev": "ts-node-dev --respawn --transpile-only src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "migrate": "ts-node src/database/migrate.ts", + "scheduler": "ts-node src/scheduler/index.ts" + }, + "keywords": [ + "bitcoin", + "lightning", + "lottery" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "axios": "^1.6.2", + "better-sqlite3": "^12.4.6", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "jsonwebtoken": "^9.0.2", + "node-cron": "^3.0.2", + "pg": "^8.11.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.10.5", + "@types/node-cron": "^3.0.11", + "@types/pg": "^8.10.9", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.3" + } +} diff --git a/back_end/src/app.ts b/back_end/src/app.ts new file mode 100644 index 0000000..d1a3619 --- /dev/null +++ b/back_end/src/app.ts @@ -0,0 +1,100 @@ +import express, { Request, Response, NextFunction } from 'express'; +import cors from 'cors'; +import swaggerUi from 'swagger-ui-express'; +import config from './config'; +import { swaggerSpec } from './config/swagger'; + +// Routes +import publicRoutes from './routes/public'; +import webhookRoutes from './routes/webhooks'; +import userRoutes from './routes/user'; +import adminRoutes from './routes/admin'; + +// Middleware +import { generalRateLimiter } from './middleware/rateLimit'; + +const app = express(); + +// Trust proxy for rate limiting (needed when behind reverse proxy) +if (config.app.nodeEnv === 'production') { + app.set('trust proxy', 1); // Trust first proxy +} + +// CORS configuration +app.use(cors({ + origin: config.app.nodeEnv === 'production' + ? config.cors.allowedOrigins + : true, + credentials: true, +})); + +// Body parser +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// General rate limiting +app.use(generalRateLimiter); + +// Swagger API Documentation +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, { + customCss: '.swagger-ui .topbar { display: none }', + customSiteTitle: 'Lightning Lottery API Docs', +})); + +// Swagger JSON endpoint +app.get('/api-docs.json', (req: Request, res: Response) => { + res.setHeader('Content-Type', 'application/json'); + res.send(swaggerSpec); +}); + +// Health check endpoint +app.get('/health', async (req: Request, res: Response) => { + const { db } = await import('./database'); + const { lnbitsService } = await import('./services/lnbits'); + + const dbHealth = await db.healthCheck(); + const lnbitsHealth = await lnbitsService.healthCheck(); + + const healthy = dbHealth && lnbitsHealth; + + res.status(healthy ? 200 : 503).json({ + version: '1.0', + status: healthy ? 'healthy' : 'unhealthy', + checks: { + database: dbHealth ? 'ok' : 'failed', + lnbits: lnbitsHealth ? 'ok' : 'failed', + }, + timestamp: new Date().toISOString(), + }); +}); + +// API routes +app.use('/', publicRoutes); +app.use('/webhooks', webhookRoutes); +app.use('/', userRoutes); +app.use('/admin', adminRoutes); + +// 404 handler +app.use((req: Request, res: Response) => { + res.status(404).json({ + version: '1.0', + error: 'NOT_FOUND', + message: 'Endpoint not found', + }); +}); + +// Error handler +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + console.error('Unhandled error:', err); + + res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: config.app.nodeEnv === 'production' + ? 'An internal error occurred' + : err.message, + }); +}); + +export default app; + diff --git a/back_end/src/config/index.ts b/back_end/src/config/index.ts new file mode 100644 index 0000000..255653d --- /dev/null +++ b/back_end/src/config/index.ts @@ -0,0 +1,159 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +const parseOriginsList = (value?: string): string[] => { + if (!value) { + return []; + } + + return value + .split(',') + .map(origin => origin.trim()) + .filter(origin => origin.length > 0); +}; + +const appPort = parseInt(process.env.PORT || process.env.APP_PORT || '3000', 10); +const appBaseUrl = process.env.APP_BASE_URL || 'http://localhost:3000'; +const nodeEnv = process.env.NODE_ENV || 'development'; + +const frontendOrigins = (() => { + const candidates = [ + process.env.FRONTEND_BASE_URL, + process.env.NEXT_PUBLIC_APP_BASE_URL, + ]; + + for (const candidate of candidates) { + const parsed = parseOriginsList(candidate); + if (parsed.length > 0) { + return parsed; + } + } + + return ['http://localhost:3001']; +})(); + +const allowedCorsOrigins = (() => { + const explicit = parseOriginsList(process.env.CORS_ALLOWED_ORIGINS); + if (explicit.length > 0) { + return explicit; + } + + return Array.from(new Set([...frontendOrigins, appBaseUrl])); +})(); + +interface Config { + app: { + port: number; + baseUrl: string; + nodeEnv: string; + }; + frontend: { + origins: string[]; + }; + cors: { + allowedOrigins: string[]; + }; + database: { + type: 'postgres' | 'sqlite'; + url: string; + }; + lnbits: { + apiBaseUrl: string; + adminKey: string; + webhookSecret: string; + }; + jwt: { + secret: string; + }; + scheduler: { + drawIntervalSeconds: number; + cycleGeneratorIntervalSeconds: number; + }; + lottery: { + defaultTicketPriceSats: number; + defaultHouseFeePercent: number; + maxTicketsPerPurchase: number; + }; + admin: { + apiKey: string; + }; + payout: { + maxAttemptsBeforeRedraw: number; + }; +} + +const config: Config = { + app: { + port: appPort, + baseUrl: appBaseUrl, + nodeEnv, + }, + frontend: { + origins: frontendOrigins, + }, + cors: { + allowedOrigins: allowedCorsOrigins, + }, + database: { + type: (process.env.DATABASE_TYPE || 'postgres') as 'postgres' | 'sqlite', + url: process.env.DATABASE_URL || + (process.env.DATABASE_TYPE === 'sqlite' + ? './data/lightning_lotto.db' + : 'postgresql://localhost:5432/lightning_lotto'), + }, + lnbits: { + apiBaseUrl: process.env.LNBITS_API_BASE_URL || '', + adminKey: process.env.LNBITS_ADMIN_KEY || '', + webhookSecret: process.env.LNBITS_WEBHOOK_SECRET || '', + }, + jwt: { + secret: process.env.JWT_SECRET || 'your-secret-key-change-this', + }, + scheduler: { + drawIntervalSeconds: parseInt(process.env.DRAW_SCHEDULER_INTERVAL_SECONDS || '60', 10), + cycleGeneratorIntervalSeconds: parseInt(process.env.CYCLE_GENERATOR_INTERVAL_SECONDS || '300', 10), + }, + lottery: { + defaultTicketPriceSats: parseInt(process.env.DEFAULT_TICKET_PRICE_SATS || '1000', 10), + defaultHouseFeePercent: parseInt(process.env.DEFAULT_HOUSE_FEE_PERCENT || '5', 10), + maxTicketsPerPurchase: 100, + }, + admin: { + apiKey: process.env.ADMIN_API_KEY || '', + }, + payout: { + maxAttemptsBeforeRedraw: parseInt(process.env.PAYOUT_MAX_ATTEMPTS || '2', 10), + }, +}; + +// Validate critical environment variables +function validateConfig(): void { + const requiredVars = [ + { key: 'LNBITS_API_BASE_URL', value: config.lnbits.apiBaseUrl }, + { key: 'LNBITS_ADMIN_KEY', value: config.lnbits.adminKey }, + { key: 'JWT_SECRET', value: config.jwt.secret }, + { key: 'ADMIN_API_KEY', value: config.admin.apiKey }, + ]; + + const missing = requiredVars.filter(v => !v.value || v.value === ''); + + if (missing.length > 0) { + console.error('❌ Missing required environment variables:'); + missing.forEach(v => console.error(` - ${v.key}`)); + process.exit(1); + } + + // Validate database type + if (!['postgres', 'sqlite'].includes(config.database.type)) { + console.error('❌ DATABASE_TYPE must be either "postgres" or "sqlite"'); + process.exit(1); + } +} + +if (config.app.nodeEnv !== 'test') { + validateConfig(); +} + +export default config; + diff --git a/back_end/src/config/swagger.ts b/back_end/src/config/swagger.ts new file mode 100644 index 0000000..bcad8cc --- /dev/null +++ b/back_end/src/config/swagger.ts @@ -0,0 +1,118 @@ +import swaggerJsdoc from 'swagger-jsdoc'; +import config from './index'; + +const swaggerDefinition = { + openapi: '3.0.0', + info: { + title: 'Lightning Lottery API', + version: '1.0.0', + description: 'Bitcoin Lightning Network powered lottery system with instant payouts', + contact: { + name: 'Lightning Lottery', + url: config.app.baseUrl, + }, + license: { + name: 'MIT', + url: 'https://opensource.org/licenses/MIT', + }, + }, + servers: [ + { + url: config.app.baseUrl, + description: config.app.nodeEnv === 'production' ? 'Production server' : 'Development server', + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: 'JWT token for Nostr authenticated users', + }, + adminKey: { + type: 'apiKey', + in: 'header', + name: 'X-Admin-Key', + description: 'Admin API key for administrative endpoints', + }, + }, + schemas: { + Lottery: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + name: { type: 'string' }, + ticket_price_sats: { type: 'integer' }, + }, + }, + JackpotCycle: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + cycle_type: { type: 'string', enum: ['hourly', 'daily', 'weekly', 'monthly'] }, + scheduled_at: { type: 'string', format: 'date-time' }, + sales_open_at: { type: 'string', format: 'date-time' }, + sales_close_at: { type: 'string', format: 'date-time' }, + status: { type: 'string', enum: ['scheduled', 'sales_open', 'drawing', 'completed', 'cancelled'] }, + pot_total_sats: { type: 'integer' }, + pot_after_fee_sats: { type: 'integer', nullable: true }, + winning_ticket_id: { type: 'string', format: 'uuid', nullable: true }, + }, + }, + TicketPurchase: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + lightning_address: { type: 'string' }, + number_of_tickets: { type: 'integer' }, + amount_sats: { type: 'integer' }, + invoice_status: { type: 'string', enum: ['pending', 'paid', 'expired', 'cancelled'] }, + ticket_issue_status: { type: 'string', enum: ['not_issued', 'issued'] }, + }, + }, + Ticket: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + serial_number: { type: 'integer' }, + is_winning_ticket: { type: 'boolean' }, + }, + }, + Error: { + type: 'object', + properties: { + version: { type: 'string', example: '1.0' }, + error: { type: 'string' }, + message: { type: 'string' }, + }, + }, + }, + }, + tags: [ + { + name: 'Public', + description: 'Public endpoints (no authentication required)', + }, + { + name: 'User', + description: 'User endpoints (Nostr authentication required)', + }, + { + name: 'Admin', + description: 'Admin endpoints (admin API key required)', + }, + { + name: 'Webhooks', + description: 'Webhook endpoints for external integrations', + }, + ], +}; + +const options = { + swaggerDefinition, + apis: ['./src/routes/*.ts', './src/controllers/*.ts'], // Path to API docs +}; + +export const swaggerSpec = swaggerJsdoc(options); + diff --git a/back_end/src/controllers/admin.ts b/back_end/src/controllers/admin.ts new file mode 100644 index 0000000..cf6e4ce --- /dev/null +++ b/back_end/src/controllers/admin.ts @@ -0,0 +1,191 @@ +import { Request, Response } from 'express'; +import { db } from '../database'; +import { executeDraw } from '../services/draw'; +import { retryPayoutService } from '../services/payout'; +import { JackpotCycle, Payout } from '../types'; + +/** + * GET /admin/cycles + * List all cycles with optional filters + */ +export async function listCycles(req: Request, res: Response) { + try { + const { status, cycle_type, limit = 50, offset = 0 } = req.query; + + let query = `SELECT * FROM jackpot_cycles WHERE 1=1`; + const params: any[] = []; + let paramCount = 0; + + if (status) { + paramCount++; + query += ` AND status = $${paramCount}`; + params.push(status); + } + + if (cycle_type) { + paramCount++; + query += ` AND cycle_type = $${paramCount}`; + params.push(cycle_type); + } + + query += ` ORDER BY scheduled_at DESC LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`; + params.push(limit, offset); + + const result = await db.query(query, params); + + const cycles = result.rows.map(c => ({ + id: c.id, + lottery_id: c.lottery_id, + cycle_type: c.cycle_type, + sequence_number: c.sequence_number, + scheduled_at: c.scheduled_at.toISOString(), + status: c.status, + pot_total_sats: parseInt(c.pot_total_sats.toString()), + pot_after_fee_sats: c.pot_after_fee_sats ? parseInt(c.pot_after_fee_sats.toString()) : null, + winning_ticket_id: c.winning_ticket_id, + winning_lightning_address: c.winning_lightning_address, + })); + + return res.json({ + version: '1.0', + data: { + cycles, + total: cycles.length, + }, + }); + } catch (error: any) { + console.error('List cycles error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to list cycles', + }); + } +} + +/** + * POST /admin/cycles/:id/run-draw + * Manually trigger draw execution + */ +export async function runDrawManually(req: Request, res: Response) { + try { + const { id } = req.params; + + const result = await executeDraw(id); + + if (!result.success) { + return res.status(400).json({ + version: '1.0', + error: result.error || 'DRAW_FAILED', + message: result.message || 'Failed to execute draw', + }); + } + + return res.json({ + version: '1.0', + data: { + cycle_id: id, + winning_ticket_id: result.winningTicketId, + pot_after_fee_sats: result.potAfterFeeSats, + payout_status: result.payoutStatus, + }, + }); + } catch (error: any) { + console.error('Manual draw error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to run draw', + }); + } +} + +/** + * GET /admin/payouts + * List payouts with optional filters + */ +export async function listPayouts(req: Request, res: Response) { + try { + const { status, limit = 50, offset = 0 } = req.query; + + let query = `SELECT * FROM payouts WHERE 1=1`; + const params: any[] = []; + let paramCount = 0; + + if (status) { + paramCount++; + query += ` AND status = $${paramCount}`; + params.push(status); + } + + query += ` ORDER BY created_at DESC LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`; + params.push(limit, offset); + + const result = await db.query(query, params); + + const payouts = result.rows.map(p => ({ + id: p.id, + lottery_id: p.lottery_id, + cycle_id: p.cycle_id, + ticket_id: p.ticket_id, + lightning_address: p.lightning_address, + amount_sats: parseInt(p.amount_sats.toString()), + status: p.status, + error_message: p.error_message, + retry_count: p.retry_count, + created_at: p.created_at.toISOString(), + })); + + return res.json({ + version: '1.0', + data: { + payouts, + total: payouts.length, + }, + }); + } catch (error: any) { + console.error('List payouts error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to list payouts', + }); + } +} + +/** + * POST /admin/payouts/:id/retry + * Retry a failed payout + */ +export async function retryPayout(req: Request, res: Response) { + try { + const { id } = req.params; + + const result = await retryPayoutService(id); + + if (!result.success) { + return res.status(400).json({ + version: '1.0', + error: result.error || 'RETRY_FAILED', + message: result.message || 'Failed to retry payout', + }); + } + + return res.json({ + version: '1.0', + data: { + payout_id: id, + status: result.status, + retry_count: result.retryCount, + }, + }); + } catch (error: any) { + console.error('Retry payout error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to retry payout', + }); + } +} + diff --git a/back_end/src/controllers/public.ts b/back_end/src/controllers/public.ts new file mode 100644 index 0000000..aabcb6f --- /dev/null +++ b/back_end/src/controllers/public.ts @@ -0,0 +1,483 @@ +import { Request, Response } from 'express'; +import { randomUUID } from 'crypto'; +import { db } from '../database'; +import { lnbitsService } from '../services/lnbits'; +import { paymentMonitor } from '../services/paymentMonitor'; +import { validateLightningAddress, validateTicketCount, sanitizeString } from '../utils/validation'; +import config from '../config'; +import { JackpotCycle, TicketPurchase, Ticket, Payout } from '../types'; +import { AuthRequest } from '../middleware/auth'; + +const toIsoString = (value: any): string => { + if (!value) { + return new Date().toISOString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (typeof value === 'number') { + return new Date(value).toISOString(); + } + + if (typeof value === 'string') { + // SQLite stores timestamps as "YYYY-MM-DD HH:mm:ss" by default + const normalized = value.includes('T') + ? value + : value.replace(' ', 'T') + 'Z'; + const parsed = new Date(normalized); + if (!isNaN(parsed.getTime())) { + return parsed.toISOString(); + } + } + + if (typeof value === 'object' && typeof (value as any).toISOString === 'function') { + return (value as any).toISOString(); + } + + const parsed = new Date(value); + if (!isNaN(parsed.getTime())) { + return parsed.toISOString(); + } + + return new Date().toISOString(); +}; + +/** + * GET /jackpot/next + * Returns the next upcoming cycle + */ +export async function getNextJackpot(req: Request, res: Response) { + try { + // Get active lottery + const lotteryResult = await db.query( + `SELECT * FROM lotteries WHERE status = 'active' LIMIT 1` + ); + + if (lotteryResult.rows.length === 0) { + return res.status(503).json({ + version: '1.0', + error: 'NO_ACTIVE_LOTTERY', + message: 'No active lottery available', + }); + } + + const lottery = lotteryResult.rows[0]; + + // Get next cycle + const cycleResult = await db.query( + `SELECT * FROM jackpot_cycles + WHERE lottery_id = $1 + AND status IN ('scheduled', 'sales_open') + AND scheduled_at > NOW() + ORDER BY scheduled_at ASC + LIMIT 1`, + [lottery.id] + ); + + if (cycleResult.rows.length === 0) { + return res.status(503).json({ + version: '1.0', + error: 'NO_UPCOMING_CYCLE', + message: 'No upcoming cycle available', + }); + } + + const cycle = cycleResult.rows[0]; + + // Helper function to ensure ISO string format + const toISOString = (date: any): string => { + if (!date) return new Date().toISOString(); + if (typeof date === 'string') return date.includes('Z') ? date : new Date(date).toISOString(); + return date.toISOString(); + }; + + return res.json({ + version: '1.0', + data: { + lottery: { + id: lottery.id, + name: lottery.name, + ticket_price_sats: parseInt(lottery.ticket_price_sats), + }, + cycle: { + id: cycle.id, + cycle_type: cycle.cycle_type, + scheduled_at: toISOString(cycle.scheduled_at), + sales_open_at: toISOString(cycle.sales_open_at), + sales_close_at: toISOString(cycle.sales_close_at), + status: cycle.status, + pot_total_sats: parseInt(cycle.pot_total_sats.toString()), + }, + }, + }); + } catch (error: any) { + console.error('Get next jackpot error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to fetch next jackpot', + }); + } +} + +/** + * POST /jackpot/buy + * Create a ticket purchase + */ +export async function buyTickets(req: AuthRequest, res: Response) { + try { + const { tickets, lightning_address, nostr_pubkey, name, buyer_name } = req.body; + const userId = req.user?.id || null; + const authNostrPubkey = req.user?.nostr_pubkey || null; + + // Validation + if (!validateTicketCount(tickets, config.lottery.maxTicketsPerPurchase)) { + return res.status(400).json({ + version: '1.0', + error: 'INVALID_TICKET_COUNT', + message: `Tickets must be between 1 and ${config.lottery.maxTicketsPerPurchase}`, + }); + } + + if (!validateLightningAddress(lightning_address)) { + return res.status(400).json({ + version: '1.0', + error: 'INVALID_LIGHTNING_ADDRESS', + message: 'Lightning address must contain @ and be valid format', + }); + } + + const normalizedLightningAddress = sanitizeString(lightning_address); + const rawNameInput = typeof name === 'string' + ? name + : typeof buyer_name === 'string' + ? buyer_name + : ''; + const buyerName = sanitizeString(rawNameInput || 'Anon', 64) || 'Anon'; + + // Get active lottery + const lotteryResult = await db.query( + `SELECT * FROM lotteries WHERE status = 'active' LIMIT 1` + ); + + if (lotteryResult.rows.length === 0) { + return res.status(503).json({ + version: '1.0', + error: 'NO_ACTIVE_LOTTERY', + message: 'No active lottery available', + }); + } + + const lottery = lotteryResult.rows[0]; + + // Get next available cycle + const cycleResult = await db.query( + `SELECT * FROM jackpot_cycles + WHERE lottery_id = $1 + AND status IN ('scheduled', 'sales_open') + AND sales_close_at > NOW() + ORDER BY scheduled_at ASC + LIMIT 1`, + [lottery.id] + ); + + if (cycleResult.rows.length === 0) { + return res.status(400).json({ + version: '1.0', + error: 'NO_AVAILABLE_CYCLE', + message: 'No cycle available for ticket purchase', + }); + } + + const cycle = cycleResult.rows[0]; + + // Calculate amount + const ticketPriceSats = parseInt(lottery.ticket_price_sats); + const amountSats = tickets * ticketPriceSats; + + // Create ticket purchase record + const purchaseId = randomUUID(); + + const purchaseResult = await db.query( + `INSERT INTO ticket_purchases ( + id, lottery_id, cycle_id, user_id, nostr_pubkey, lightning_address, + buyer_name, + number_of_tickets, ticket_price_sats, amount_sats, + lnbits_invoice_id, lnbits_payment_hash, invoice_status, ticket_issue_status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + RETURNING *`, + [ + purchaseId, + lottery.id, + cycle.id, + userId, + nostr_pubkey || authNostrPubkey || null, + normalizedLightningAddress, + buyerName, + tickets, + ticketPriceSats, + amountSats, + '', // Will update after invoice creation + '', // Will update after invoice creation + 'pending', + 'not_issued', + ] + ); + + const purchase = purchaseResult.rows[0]; + const publicUrl = `${config.app.baseUrl}/tickets/${purchase.id}`; + + // Create invoice memo + const memo = `Lightning Jackpot +Tickets: ${tickets} +Purchase ID: ${purchase.id} +Check status: ${publicUrl}`; + + // Create LNbits invoice + try { + const webhookUrl = new URL('/webhooks/lnbits/payment', config.app.baseUrl); + if (config.lnbits.webhookSecret) { + webhookUrl.searchParams.set('secret', config.lnbits.webhookSecret); + } + + const invoice = await lnbitsService.createInvoice({ + amount: amountSats, + memo: memo, + webhook: webhookUrl.toString(), + }); + + // Update purchase with invoice details + await db.query( + `UPDATE ticket_purchases + SET lnbits_invoice_id = $1, lnbits_payment_hash = $2, updated_at = NOW() + WHERE id = $3`, + [invoice.checking_id, invoice.payment_hash, purchase.id] + ); + + if (userId) { + await db.query( + `UPDATE users + SET lightning_address = $1, updated_at = NOW() + WHERE id = $2`, + [normalizedLightningAddress, userId] + ); + } + + paymentMonitor.addInvoice(purchase.id, invoice.payment_hash); + + return res.json({ + version: '1.0', + data: { + ticket_purchase_id: purchase.id, + public_url: publicUrl, + invoice: { + payment_request: invoice.payment_request, + amount_sats: amountSats, + }, + }, + }); + } catch (invoiceError: any) { + // Cleanup purchase if invoice creation fails + await db.query('DELETE FROM ticket_purchases WHERE id = $1', [purchase.id]); + + return res.status(502).json({ + version: '1.0', + error: 'INVOICE_CREATION_FAILED', + message: 'Failed to create Lightning invoice', + }); + } + } catch (error: any) { + console.error('Buy tickets error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to process ticket purchase', + }); + } +} + +/** + * GET /tickets/:id + * Get ticket purchase status + */ +export async function getTicketStatus(req: Request, res: Response) { + try { + const { id } = req.params; + + // Get purchase + const purchaseResult = await db.query( + `SELECT * FROM ticket_purchases WHERE id = $1`, + [id] + ); + + if (purchaseResult.rows.length === 0) { + return res.status(404).json({ + version: '1.0', + error: 'PURCHASE_NOT_FOUND', + message: 'Ticket purchase not found', + }); + } + + const purchase = purchaseResult.rows[0]; + + // Get cycle + const cycleResult = await db.query( + `SELECT * FROM jackpot_cycles WHERE id = $1`, + [purchase.cycle_id] + ); + + const cycle = cycleResult.rows[0]; + + // Get tickets + const ticketsResult = await db.query( + `SELECT * FROM tickets WHERE ticket_purchase_id = $1 ORDER BY serial_number`, + [id] + ); + + const tickets = ticketsResult.rows.map(t => ({ + id: t.id, + serial_number: parseInt(t.serial_number.toString()), + is_winning_ticket: cycle.winning_ticket_id === t.id, + })); + + // Determine result + let result = { + has_drawn: cycle.status === 'completed', + is_winner: false, + payout: null as any, + }; + + if (cycle.status === 'completed' && cycle.winning_ticket_id) { + const isWinner = tickets.some(t => t.id === cycle.winning_ticket_id); + result.is_winner = isWinner; + + if (isWinner) { + // Get payout + const payoutResult = await db.query( + `SELECT * FROM payouts WHERE ticket_id = $1`, + [cycle.winning_ticket_id] + ); + + if (payoutResult.rows.length > 0) { + const payout = payoutResult.rows[0]; + result.payout = { + status: payout.status, + amount_sats: parseInt(payout.amount_sats.toString()), + }; + } + } + } + + return res.json({ + version: '1.0', + data: { + purchase: { + id: purchase.id, + lottery_id: purchase.lottery_id, + cycle_id: purchase.cycle_id, + lightning_address: purchase.lightning_address, + buyer_name: purchase.buyer_name, + number_of_tickets: purchase.number_of_tickets, + ticket_price_sats: parseInt(purchase.ticket_price_sats.toString()), + amount_sats: parseInt(purchase.amount_sats.toString()), + invoice_status: purchase.invoice_status, + ticket_issue_status: purchase.ticket_issue_status, + created_at: toIsoString(purchase.created_at), + }, + tickets, + cycle: { + id: cycle.id, + cycle_type: cycle.cycle_type, + scheduled_at: toIsoString(cycle.scheduled_at), + sales_open_at: toIsoString(cycle.sales_open_at), + sales_close_at: toIsoString(cycle.sales_close_at), + status: cycle.status, + pot_total_sats: parseInt(cycle.pot_total_sats.toString()), + pot_after_fee_sats: cycle.pot_after_fee_sats ? parseInt(cycle.pot_after_fee_sats.toString()) : null, + winning_ticket_id: cycle.winning_ticket_id, + }, + result, + }, + }); + } catch (error: any) { + console.error('Get ticket status error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to fetch ticket status', + }); + } +} + +interface PastWinRow { + cycle_id: string; + cycle_type: JackpotCycle['cycle_type']; + scheduled_at: Date | string; + pot_total_sats: number; + pot_after_fee_sats: number | null; + buyer_name: string | null; + serial_number: number | null; +} + +/** + * GET /jackpot/past-wins + * List past jackpots with winner info + */ +export async function getPastWins(req: Request, res: Response) { + try { + const limitParam = parseInt((req.query.limit as string) || '20', 10); + const offsetParam = parseInt((req.query.offset as string) || '0', 10); + const limit = Math.min(100, Math.max(1, Number.isNaN(limitParam) ? 20 : limitParam)); + const offset = Math.max(0, Number.isNaN(offsetParam) ? 0 : offsetParam); + + const result = await db.query( + `SELECT + jc.id as cycle_id, + jc.cycle_type, + jc.scheduled_at, + jc.pot_total_sats, + jc.pot_after_fee_sats, + tp.buyer_name, + t.serial_number + FROM jackpot_cycles jc + JOIN tickets t ON jc.winning_ticket_id = t.id + JOIN ticket_purchases tp ON t.ticket_purchase_id = tp.id + WHERE jc.status = 'completed' + AND jc.winning_ticket_id IS NOT NULL + ORDER BY jc.scheduled_at DESC + LIMIT $1 OFFSET $2`, + [limit, offset] + ); + + const wins = result.rows.map((row) => ({ + cycle_id: row.cycle_id, + cycle_type: row.cycle_type, + scheduled_at: toIsoString(row.scheduled_at), + pot_total_sats: parseInt(row.pot_total_sats.toString()), + pot_after_fee_sats: row.pot_after_fee_sats + ? parseInt(row.pot_after_fee_sats.toString()) + : null, + winner_name: row.buyer_name || 'Anon', + winning_ticket_serial: row.serial_number + ? parseInt(row.serial_number.toString()) + : null, + })); + + return res.json({ + version: '1.0', + data: { + wins, + }, + }); + } catch (error: any) { + console.error('Get past wins error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to load past wins', + }); + } +} + diff --git a/back_end/src/controllers/user.ts b/back_end/src/controllers/user.ts new file mode 100644 index 0000000..37b1e23 --- /dev/null +++ b/back_end/src/controllers/user.ts @@ -0,0 +1,348 @@ +import { Request, Response } from 'express'; +import { randomUUID } from 'crypto'; +import jwt from 'jsonwebtoken'; +import { db } from '../database'; +import { AuthRequest } from '../middleware/auth'; +import { validateNostrPubkey, validateLightningAddress } from '../utils/validation'; +import config from '../config'; +import { User, TicketPurchase, Payout } from '../types'; + +const toIsoString = (value: any): string => { + if (!value) { + return new Date().toISOString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (typeof value === 'number') { + return new Date(value).toISOString(); + } + + if (typeof value === 'string') { + const normalized = value.includes('T') ? value : value.replace(' ', 'T') + 'Z'; + const parsed = new Date(normalized); + if (!isNaN(parsed.getTime())) { + return parsed.toISOString(); + } + } + + if (typeof value === 'object' && typeof (value as any).toISOString === 'function') { + return (value as any).toISOString(); + } + + const parsed = new Date(value); + if (!isNaN(parsed.getTime())) { + return parsed.toISOString(); + } + + return new Date().toISOString(); +}; + +const parseCount = (value: any): number => { + const parsed = parseInt(value?.toString() ?? '0', 10); + return Number.isNaN(parsed) ? 0 : parsed; +}; + +/** + * POST /auth/nostr + * Authenticate with Nostr signature + */ +export async function nostrAuth(req: Request, res: Response) { + try { + const { nostr_pubkey, signed_message, nonce } = req.body; + + // Validate pubkey format + if (!validateNostrPubkey(nostr_pubkey)) { + return res.status(400).json({ + version: '1.0', + error: 'INVALID_PUBKEY', + message: 'Invalid Nostr public key format', + }); + } + + // TODO: Implement actual signature verification using nostr-tools + // For now, we'll trust the pubkey (NOT PRODUCTION READY) + // In production, verify the signature against the nonce + + // Find or create user + let userResult = await db.query( + `SELECT * FROM users WHERE nostr_pubkey = $1`, + [nostr_pubkey] + ); + + let user: User; + + if (userResult.rows.length === 0) { + // Create new user + const userId = randomUUID(); + const createResult = await db.query( + `INSERT INTO users (id, nostr_pubkey) VALUES ($1, $2) RETURNING *`, + [userId, nostr_pubkey] + ); + user = createResult.rows[0]; + } else { + user = userResult.rows[0]; + } + + // Generate JWT token + const token = jwt.sign( + { + id: user.id, + nostr_pubkey: user.nostr_pubkey, + }, + config.jwt.secret, + { expiresIn: '30d' } + ); + + return res.json({ + version: '1.0', + data: { + token, + user: { + id: user.id, + nostr_pubkey: user.nostr_pubkey, + display_name: user.display_name, + lightning_address: user.lightning_address, + }, + }, + }); + } catch (error: any) { + console.error('Nostr auth error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to authenticate', + }); + } +} + +/** + * GET /me + * Get user profile + */ +export async function getProfile(req: AuthRequest, res: Response) { + try { + const userId = req.user!.id; + + const userResult = await db.query( + `SELECT * FROM users WHERE id = $1`, + [userId] + ); + + if (userResult.rows.length === 0) { + return res.status(404).json({ + version: '1.0', + error: 'USER_NOT_FOUND', + message: 'User not found', + }); + } + + const user = userResult.rows[0]; + + const nostrPubkey = user.nostr_pubkey; + + const totalTicketsResult = await db.query( + `SELECT COALESCE(SUM(number_of_tickets), 0) as total_tickets + FROM ticket_purchases + WHERE user_id = $1 OR nostr_pubkey = $2`, + [userId, nostrPubkey] + ); + + const currentRoundResult = await db.query( + `SELECT COALESCE(SUM(tp.number_of_tickets), 0) as current_tickets + FROM ticket_purchases tp + JOIN jackpot_cycles jc ON tp.cycle_id = jc.id + WHERE (tp.user_id = $1 OR tp.nostr_pubkey = $2) + AND jc.status IN ('scheduled', 'sales_open')`, + [userId, nostrPubkey] + ); + + const pastTicketsResult = await db.query( + `SELECT COALESCE(SUM(tp.number_of_tickets), 0) as past_tickets + FROM ticket_purchases tp + JOIN jackpot_cycles jc ON tp.cycle_id = jc.id + WHERE (tp.user_id = $1 OR tp.nostr_pubkey = $2) + AND jc.status NOT IN ('scheduled', 'sales_open')`, + [userId, nostrPubkey] + ); + + const winStats = await db.query( + `SELECT + COALESCE(SUM(CASE WHEN p.status = 'paid' THEN 1 ELSE 0 END), 0) as total_wins, + COALESCE(SUM(CASE WHEN p.status = 'paid' THEN p.amount_sats ELSE 0 END), 0) as total_winnings + FROM payouts p + JOIN tickets t ON p.ticket_id = t.id + JOIN ticket_purchases tp ON t.ticket_purchase_id = tp.id + WHERE tp.user_id = $1 OR tp.nostr_pubkey = $2`, + [userId, nostrPubkey] + ); + + const totalTickets = parseCount(totalTicketsResult.rows[0]?.total_tickets); + const currentRoundTickets = parseCount(currentRoundResult.rows[0]?.current_tickets); + const pastTickets = parseCount(pastTicketsResult.rows[0]?.past_tickets); + + return res.json({ + version: '1.0', + data: { + user: { + id: user.id, + nostr_pubkey: user.nostr_pubkey, + display_name: user.display_name, + lightning_address: user.lightning_address, + }, + stats: { + total_tickets: totalTickets, + current_round_tickets: currentRoundTickets, + past_tickets: pastTickets, + total_wins: parseCount(winStats.rows[0]?.total_wins), + total_winnings_sats: parseCount(winStats.rows[0]?.total_winnings), + }, + }, + }); + } catch (error: any) { + console.error('Get profile error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to get profile', + }); + } +} + +/** + * PATCH /me/lightning-address + * Update user's lightning address + */ +export async function updateLightningAddress(req: AuthRequest, res: Response) { + try { + const userId = req.user!.id; + const { lightning_address } = req.body; + + if (!validateLightningAddress(lightning_address)) { + return res.status(400).json({ + version: '1.0', + error: 'INVALID_LIGHTNING_ADDRESS', + message: 'Invalid Lightning Address format', + }); + } + + await db.query( + `UPDATE users SET lightning_address = $1, updated_at = NOW() WHERE id = $2`, + [lightning_address, userId] + ); + + return res.json({ + version: '1.0', + data: { + lightning_address, + }, + }); + } catch (error: any) { + console.error('Update lightning address error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to update lightning address', + }); + } +} + +/** + * GET /me/tickets + * Get user's ticket purchases + */ +export async function getUserTickets(req: AuthRequest, res: Response) { + try { + const userId = req.user!.id; + const userPubkey = req.user!.nostr_pubkey; + const limitParam = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : 50; + const offsetParam = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : 0; + const limitValue = Math.min(100, Math.max(1, Number.isNaN(limitParam) ? 50 : limitParam)); + const offsetValue = Math.max(0, Number.isNaN(offsetParam) ? 0 : offsetParam); + + const result = await db.query( + `SELECT tp.*, jc.cycle_type, jc.scheduled_at, jc.status as cycle_status + FROM ticket_purchases tp + JOIN jackpot_cycles jc ON tp.cycle_id = jc.id + WHERE (tp.user_id = $1 OR tp.nostr_pubkey = $2) + ORDER BY tp.created_at DESC + LIMIT $3 OFFSET $4`, + [userId, userPubkey, limitValue, offsetValue] + ); + + const purchases = result.rows.map(p => ({ + id: p.id, + cycle_id: p.cycle_id, + buyer_name: p.buyer_name, + number_of_tickets: p.number_of_tickets, + amount_sats: parseInt(p.amount_sats.toString()), + invoice_status: p.invoice_status, + ticket_issue_status: p.ticket_issue_status, + created_at: toIsoString(p.created_at), + })); + + return res.json({ + version: '1.0', + data: { + purchases, + total: purchases.length, + }, + }); + } catch (error: any) { + console.error('Get user tickets error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to get tickets', + }); + } +} + +/** + * GET /me/wins + * Get user's wins + */ +export async function getUserWins(req: AuthRequest, res: Response) { + try { + const userId = req.user!.id; + const userPubkey = req.user!.nostr_pubkey; + + const result = await db.query( + `SELECT p.*, jc.cycle_type, jc.scheduled_at + FROM payouts p + JOIN tickets t ON p.ticket_id = t.id + JOIN ticket_purchases tp ON t.ticket_purchase_id = tp.id + JOIN jackpot_cycles jc ON p.cycle_id = jc.id + WHERE tp.user_id = $1 OR tp.nostr_pubkey = $2 + ORDER BY p.created_at DESC`, + [userId, userPubkey] + ); + + const wins = result.rows.map(p => ({ + id: p.id, + cycle_id: p.cycle_id, + ticket_id: p.ticket_id, + amount_sats: parseInt(p.amount_sats.toString()), + status: p.status, + created_at: toIsoString(p.created_at), + })); + + return res.json({ + version: '1.0', + data: { + wins, + total: wins.length, + }, + }); + } catch (error: any) { + console.error('Get user wins error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to get wins', + }); + } +} + diff --git a/back_end/src/controllers/webhooks.ts b/back_end/src/controllers/webhooks.ts new file mode 100644 index 0000000..6919abf --- /dev/null +++ b/back_end/src/controllers/webhooks.ts @@ -0,0 +1,98 @@ +import { Request, Response } from 'express'; +import { db } from '../database'; +import { lnbitsService } from '../services/lnbits'; +import { TicketPurchase } from '../types'; +import { finalizeTicketPurchase } from '../services/paymentProcessor'; +import { paymentMonitor } from '../services/paymentMonitor'; + +/** + * POST /webhooks/lnbits/payment + * Handle LNbits payment webhook + */ +export async function handleLNbitsPayment(req: Request, res: Response) { + try { + const { payment_hash, amount, paid } = req.body; + + // Verify webhook (basic secret validation) + const webhookSecretHeader = (req.headers['x-webhook-secret'] || req.headers['x-callback-secret']) as string | undefined; + const webhookSecretQuery = (() => { + const value = req.query?.secret; + if (Array.isArray(value)) { + return value[0]; + } + return value as string | undefined; + })(); + + const providedSecret = webhookSecretHeader || webhookSecretQuery || ''; + + if (!lnbitsService.verifyWebhook(providedSecret)) { + console.error('Webhook verification failed'); + return res.status(403).json({ + version: '1.0', + error: 'FORBIDDEN', + message: 'Invalid webhook signature', + }); + } + + // Check if payment is successful + if (!paid) { + console.log('Payment not completed yet:', payment_hash); + return res.json({ version: '1.0', message: 'Payment not completed' }); + } + + // Find purchase by payment hash + const purchaseResult = await db.query( + `SELECT * FROM ticket_purchases WHERE lnbits_payment_hash = $1`, + [payment_hash] + ); + + if (purchaseResult.rows.length === 0) { + console.error('Purchase not found for payment hash:', payment_hash); + // Return 200 to avoid retries for unknown payments + return res.json({ version: '1.0', message: 'Purchase not found' }); + } + + const purchase = purchaseResult.rows[0]; + + // Idempotency check + if (purchase.invoice_status === 'paid') { + console.log('Invoice already marked as paid:', purchase.id); + return res.json({ version: '1.0', message: 'Already processed' }); + } + + // Verify amount (optional, but recommended) + const expectedAmount = parseInt(purchase.amount_sats.toString()); + if (amount && amount < expectedAmount) { + console.error('Payment amount mismatch:', { expected: expectedAmount, received: amount }); + // You could mark as error here, but for now we'll still process + } + + const finalizeResult = await finalizeTicketPurchase(purchase.id); + if (finalizeResult.status === 'not_found') { + console.error('Purchase disappeared before processing:', purchase.id); + return res.json({ version: '1.0', message: 'Purchase missing during processing' }); + } + + paymentMonitor.removeInvoice(purchase.id); + + console.log('Payment processed successfully:', { + purchase_id: purchase.id, + tickets: finalizeResult.ticketsIssued, + amount_sats: purchase.amount_sats, + }); + + return res.json({ + version: '1.0', + message: 'Payment processed successfully', + }); + + } catch (error: any) { + console.error('Webhook processing error:', error); + return res.status(500).json({ + version: '1.0', + error: 'INTERNAL_ERROR', + message: 'Failed to process payment webhook', + }); + } +} + diff --git a/back_end/src/database/index.ts b/back_end/src/database/index.ts new file mode 100644 index 0000000..c7b6cd3 --- /dev/null +++ b/back_end/src/database/index.ts @@ -0,0 +1,334 @@ +import { Pool, QueryResult, PoolClient, QueryResultRow } from 'pg'; +import Database from 'better-sqlite3'; +import config from '../config'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface DbClient { + query(text: string, params?: any[]): Promise>; + release?: () => void; +} + +class DatabaseWrapper { + private pgPool?: Pool; + private sqliteDb?: Database.Database; + private dbType: 'postgres' | 'sqlite'; + + constructor() { + this.dbType = config.database.type; + + if (this.dbType === 'postgres') { + this.initPostgres(); + } else { + this.initSqlite(); + } + } + + private initPostgres() { + this.pgPool = new Pool({ + connectionString: config.database.url, + ssl: config.app.nodeEnv === 'production' ? { rejectUnauthorized: false } : false, + }); + + this.pgPool.on('error', (err) => { + console.error('Unexpected PostgreSQL error:', err); + }); + + console.log('✓ PostgreSQL database initialized'); + } + + private initSqlite() { + // Ensure data directory exists + const dbPath = config.database.url; + const dbDir = path.dirname(dbPath); + + if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }); + } + + this.sqliteDb = new Database(dbPath); + this.sqliteDb.pragma('journal_mode = WAL'); + this.sqliteDb.pragma('foreign_keys = ON'); + + // Initialize schema for SQLite + this.initSqliteSchema(); + + console.log(`✓ SQLite database initialized at: ${dbPath}`); + } + + private initSqliteSchema() { + if (!this.sqliteDb) return; + + const schema = ` + -- SQLite Schema for Lightning Lottery + + CREATE TABLE IF NOT EXISTS lotteries ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('active', 'paused', 'finished')), + ticket_price_sats INTEGER NOT NULL, + fee_percent INTEGER NOT NULL CHECK (fee_percent >= 0 AND fee_percent <= 100), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS jackpot_cycles ( + id TEXT PRIMARY KEY, + lottery_id TEXT NOT NULL REFERENCES lotteries(id), + cycle_type TEXT NOT NULL CHECK (cycle_type IN ('hourly', 'daily', 'weekly', 'monthly')), + sequence_number INTEGER NOT NULL, + scheduled_at TEXT NOT NULL, + sales_open_at TEXT NOT NULL, + sales_close_at TEXT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('scheduled', 'sales_open', 'drawing', 'completed', 'cancelled')), + pot_total_sats INTEGER NOT NULL DEFAULT 0, + pot_after_fee_sats INTEGER, + winning_ticket_id TEXT, + winning_lightning_address TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + nostr_pubkey TEXT UNIQUE NOT NULL, + display_name TEXT, + lightning_address TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS ticket_purchases ( + id TEXT PRIMARY KEY, + lottery_id TEXT NOT NULL REFERENCES lotteries(id), + cycle_id TEXT NOT NULL REFERENCES jackpot_cycles(id), + user_id TEXT REFERENCES users(id), + nostr_pubkey TEXT, + lightning_address TEXT NOT NULL, + buyer_name TEXT NOT NULL DEFAULT 'Anon', + number_of_tickets INTEGER NOT NULL, + ticket_price_sats INTEGER NOT NULL, + amount_sats INTEGER NOT NULL, + lnbits_invoice_id TEXT NOT NULL, + lnbits_payment_hash TEXT NOT NULL, + invoice_status TEXT NOT NULL CHECK (invoice_status IN ('pending', 'paid', 'expired', 'cancelled')), + ticket_issue_status TEXT NOT NULL CHECK (ticket_issue_status IN ('not_issued', 'issued')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS tickets ( + id TEXT PRIMARY KEY, + lottery_id TEXT NOT NULL REFERENCES lotteries(id), + cycle_id TEXT NOT NULL REFERENCES jackpot_cycles(id), + ticket_purchase_id TEXT NOT NULL REFERENCES ticket_purchases(id), + user_id TEXT REFERENCES users(id), + lightning_address TEXT NOT NULL, + serial_number INTEGER NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS payouts ( + id TEXT PRIMARY KEY, + lottery_id TEXT NOT NULL REFERENCES lotteries(id), + cycle_id TEXT NOT NULL REFERENCES jackpot_cycles(id), + ticket_id TEXT NOT NULL REFERENCES tickets(id), + user_id TEXT REFERENCES users(id), + lightning_address TEXT NOT NULL, + amount_sats INTEGER NOT NULL, + status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'failed')), + lnbits_payment_id TEXT, + error_message TEXT, + retry_count INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS draw_logs ( + id TEXT PRIMARY KEY, + cycle_id TEXT NOT NULL REFERENCES jackpot_cycles(id), + number_of_tickets INTEGER NOT NULL, + pot_total_sats INTEGER NOT NULL, + fee_percent INTEGER NOT NULL, + pot_after_fee_sats INTEGER NOT NULL, + winner_ticket_id TEXT, + winner_lightning_address TEXT, + rng_source TEXT NOT NULL, + selected_index INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + -- Create indexes + CREATE INDEX IF NOT EXISTS idx_cycles_status_time ON jackpot_cycles(status, scheduled_at); + CREATE INDEX IF NOT EXISTS idx_ticketpurchase_paymenthash ON ticket_purchases(lnbits_payment_hash); + CREATE INDEX IF NOT EXISTS idx_ticketpurchase_cycle ON ticket_purchases(cycle_id); + CREATE INDEX IF NOT EXISTS idx_tickets_cycle ON tickets(cycle_id); + CREATE INDEX IF NOT EXISTS idx_tickets_purchase ON tickets(ticket_purchase_id); + CREATE INDEX IF NOT EXISTS idx_payouts_ticket ON payouts(ticket_id); + CREATE INDEX IF NOT EXISTS idx_payouts_status ON payouts(status); + CREATE INDEX IF NOT EXISTS idx_users_pubkey ON users(nostr_pubkey); + + -- Insert default lottery if not exists + INSERT OR IGNORE INTO lotteries (id, name, status, ticket_price_sats, fee_percent) + VALUES ('default-lottery-id', 'Main Lightning Jackpot', 'active', 1000, 5); + `; + + this.sqliteDb.exec(schema); + this.ensureSqliteColumn('ticket_purchases', 'buyer_name', "TEXT NOT NULL DEFAULT 'Anon'"); + } + + async query(text: string, params?: any[]): Promise> { + const start = Date.now(); + + try { + if (this.dbType === 'postgres' && this.pgPool) { + const res = await this.pgPool.query(text, params); + const duration = Date.now() - start; + if (config.app.nodeEnv === 'development') { + console.log('Executed query', { text: text.substring(0, 100), duration, rows: res.rowCount }); + } + return res; + } else if (this.dbType === 'sqlite' && this.sqliteDb) { + // Convert PostgreSQL-style query to SQLite + let sqliteQuery = this.convertToSqlite(text, params); + + // Determine if it's a SELECT or modification query + const isSelect = text.trim().toUpperCase().startsWith('SELECT'); + + let rows: any[] = []; + + if (isSelect) { + const stmt = this.sqliteDb.prepare(sqliteQuery.text); + rows = stmt.all(...(sqliteQuery.params || [])); + } else { + const stmt = this.sqliteDb.prepare(sqliteQuery.text); + const result = stmt.run(...(sqliteQuery.params || [])); + + // For INSERT with RETURNING, we need to fetch the inserted row + if (text.toUpperCase().includes('RETURNING')) { + const match = text.match(/INSERT INTO (\w+)/i); + if (match && result.lastInsertRowid) { + const tableName = match[1]; + rows = this.sqliteDb.prepare(`SELECT * FROM ${tableName} WHERE rowid = ?`) + .all(result.lastInsertRowid); + } + } + } + + const duration = Date.now() - start; + if (config.app.nodeEnv === 'development') { + console.log('Executed SQLite query', { text: sqliteQuery.text.substring(0, 100), duration, rows: rows.length }); + } + + return { + rows: rows as T[], + rowCount: rows.length, + command: '', + oid: 0, + fields: [], + } as QueryResult; + } + + throw new Error('Database not initialized'); + } catch (error) { + console.error('Database query error:', error); + throw error; + } + } + + private generateUUID(): string { + // Simple UUID v4 generator + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + + private convertToSqlite(text: string, params?: any[]): { text: string; params?: any[] } { + // Convert PostgreSQL $1, $2 placeholders to SQLite ? + let sqliteText = text; + let sqliteParams = params || []; + + // Replace $n with ? + sqliteText = sqliteText.replace(/\$(\d+)/g, '?'); + + // Convert PostgreSQL-specific functions + sqliteText = sqliteText.replace(/NOW\(\)/gi, "datetime('now')"); + + // Remove FOR UPDATE locking hints (SQLite locks entire database for writes) + sqliteText = sqliteText.replace(/\s+FOR\s+UPDATE/gi, ''); + + // For UUID generation in INSERT statements, generate actual UUIDs + const uuidMatches = sqliteText.match(/uuid_generate_v4\(\)/gi); + if (uuidMatches) { + uuidMatches.forEach(() => { + sqliteText = sqliteText.replace(/uuid_generate_v4\(\)/i, `'${this.generateUUID()}'`); + }); + } + + // Convert RETURNING * to just the statement (SQLite doesn't support RETURNING in the same way) + if (sqliteText.toUpperCase().includes('RETURNING')) { + sqliteText = sqliteText.replace(/RETURNING \*/gi, ''); + } + + // Handle TIMESTAMPTZ -> TEXT conversion + sqliteText = sqliteText.replace(/TIMESTAMPTZ/gi, 'TEXT'); + + return { text: sqliteText, params: sqliteParams }; + } + + private ensureSqliteColumn(table: string, column: string, definition: string): void { + if (!this.sqliteDb) return; + const columns = this.sqliteDb.prepare(`PRAGMA table_info(${table})`).all(); + const exists = columns.some((col: any) => col.name === column); + if (!exists) { + this.sqliteDb.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`); + } + } + + async getClient(): Promise { + if (this.dbType === 'postgres' && this.pgPool) { + const client = await this.pgPool.connect(); + return { + query: async (text: string, params?: any[]) => { + return client.query(text, params); + }, + release: () => client.release(), + }; + } else { + // For SQLite, return a wrapper that uses the main connection + return { + query: async (text: string, params?: any[]) => { + return this.query(text, params); + }, + }; + } + } + + async healthCheck(): Promise { + try { + if (this.dbType === 'postgres') { + await this.query('SELECT 1'); + } else { + await this.query('SELECT 1'); + } + return true; + } catch (error) { + console.error('Database health check failed:', error); + return false; + } + } + + async close(): Promise { + if (this.pgPool) { + await this.pgPool.end(); + } + if (this.sqliteDb) { + this.sqliteDb.close(); + } + } +} + +export const db = new DatabaseWrapper(); +export default db; diff --git a/back_end/src/database/schema.sql b/back_end/src/database/schema.sql new file mode 100644 index 0000000..c467719 --- /dev/null +++ b/back_end/src/database/schema.sql @@ -0,0 +1,131 @@ +-- Lightning Lottery Database Schema +-- PostgreSQL 14+ + +-- Create UUID extension if not exists +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Lotteries table +CREATE TABLE IF NOT EXISTS lotteries ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('active', 'paused', 'finished')), + ticket_price_sats BIGINT NOT NULL, + fee_percent INTEGER NOT NULL CHECK (fee_percent >= 0 AND fee_percent <= 100), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Jackpot Cycles table +CREATE TABLE IF NOT EXISTS jackpot_cycles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + lottery_id UUID NOT NULL REFERENCES lotteries(id), + cycle_type TEXT NOT NULL CHECK (cycle_type IN ('hourly', 'daily', 'weekly', 'monthly')), + sequence_number INTEGER NOT NULL, + scheduled_at TIMESTAMPTZ NOT NULL, + sales_open_at TIMESTAMPTZ NOT NULL, + sales_close_at TIMESTAMPTZ NOT NULL, + status TEXT NOT NULL CHECK (status IN ('scheduled', 'sales_open', 'drawing', 'completed', 'cancelled')), + pot_total_sats BIGINT NOT NULL DEFAULT 0, + pot_after_fee_sats BIGINT, + winning_ticket_id UUID, + winning_lightning_address TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Users table (optional for Nostr) +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + nostr_pubkey TEXT UNIQUE NOT NULL, + display_name TEXT, + lightning_address TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Ticket Purchases table +CREATE TABLE IF NOT EXISTS ticket_purchases ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + lottery_id UUID NOT NULL REFERENCES lotteries(id), + cycle_id UUID NOT NULL REFERENCES jackpot_cycles(id), + user_id UUID REFERENCES users(id), + nostr_pubkey TEXT, + lightning_address TEXT NOT NULL, + buyer_name TEXT NOT NULL DEFAULT 'Anon', + number_of_tickets INTEGER NOT NULL, + ticket_price_sats BIGINT NOT NULL, + amount_sats BIGINT NOT NULL, + lnbits_invoice_id TEXT NOT NULL, + lnbits_payment_hash TEXT NOT NULL, + invoice_status TEXT NOT NULL CHECK (invoice_status IN ('pending', 'paid', 'expired', 'cancelled')), + ticket_issue_status TEXT NOT NULL CHECK (ticket_issue_status IN ('not_issued', 'issued')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Tickets table +CREATE TABLE IF NOT EXISTS tickets ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + lottery_id UUID NOT NULL REFERENCES lotteries(id), + cycle_id UUID NOT NULL REFERENCES jackpot_cycles(id), + ticket_purchase_id UUID NOT NULL REFERENCES ticket_purchases(id), + user_id UUID REFERENCES users(id), + lightning_address TEXT NOT NULL, + serial_number BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Payouts table +CREATE TABLE IF NOT EXISTS payouts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + lottery_id UUID NOT NULL REFERENCES lotteries(id), + cycle_id UUID NOT NULL REFERENCES jackpot_cycles(id), + ticket_id UUID NOT NULL REFERENCES tickets(id), + user_id UUID REFERENCES users(id), + lightning_address TEXT NOT NULL, + amount_sats BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'failed')), + lnbits_payment_id TEXT, + error_message TEXT, + retry_count INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Draw logs table (for audit and transparency) +CREATE TABLE IF NOT EXISTS draw_logs ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + cycle_id UUID NOT NULL REFERENCES jackpot_cycles(id), + number_of_tickets INTEGER NOT NULL, + pot_total_sats BIGINT NOT NULL, + fee_percent INTEGER NOT NULL, + pot_after_fee_sats BIGINT NOT NULL, + winner_ticket_id UUID, + winner_lightning_address TEXT, + rng_source TEXT NOT NULL, + selected_index INTEGER, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Indexes for performance +CREATE INDEX IF NOT EXISTS idx_cycles_status_time ON jackpot_cycles(status, scheduled_at); +CREATE INDEX IF NOT EXISTS idx_ticketpurchase_paymenthash ON ticket_purchases(lnbits_payment_hash); +CREATE INDEX IF NOT EXISTS idx_ticketpurchase_cycle ON ticket_purchases(cycle_id); +CREATE INDEX IF NOT EXISTS idx_tickets_cycle ON tickets(cycle_id); +CREATE INDEX IF NOT EXISTS idx_tickets_purchase ON tickets(ticket_purchase_id); +CREATE INDEX IF NOT EXISTS idx_payouts_ticket ON payouts(ticket_id); +CREATE INDEX IF NOT EXISTS idx_payouts_status ON payouts(status); +CREATE INDEX IF NOT EXISTS idx_users_pubkey ON users(nostr_pubkey); + +-- Add foreign key constraint for winning ticket (after tickets table exists) +ALTER TABLE jackpot_cycles ADD CONSTRAINT fk_winning_ticket + FOREIGN KEY (winning_ticket_id) REFERENCES tickets(id) ON DELETE SET NULL; + +ALTER TABLE ticket_purchases + ADD COLUMN IF NOT EXISTS buyer_name TEXT NOT NULL DEFAULT 'Anon'; + +-- Insert default lottery +INSERT INTO lotteries (name, status, ticket_price_sats, fee_percent) +VALUES ('Main Lightning Jackpot', 'active', 1000, 5) +ON CONFLICT DO NOTHING; + diff --git a/back_end/src/index.ts b/back_end/src/index.ts new file mode 100644 index 0000000..c751d4a --- /dev/null +++ b/back_end/src/index.ts @@ -0,0 +1,63 @@ +import app from './app'; +import config from './config'; +import { db } from './database'; +import { startSchedulers } from './scheduler'; +import { paymentMonitor } from './services/paymentMonitor'; + +async function start() { + try { + // Test database connection + console.log('Testing database connection...'); + const dbHealthy = await db.healthCheck(); + + if (!dbHealthy) { + console.error('❌ Database connection failed'); + process.exit(1); + } + + console.log('✓ Database connected'); + + // Start HTTP server + app.listen(config.app.port, () => { + console.log(` +╔═══════════════════════════════════════════╗ +║ Lightning Lottery API Server ║ +║ ║ +║ Port: ${config.app.port} ║ +║ Environment: ${config.app.nodeEnv} ║ +║ Base URL: ${config.app.baseUrl} ║ +╚═══════════════════════════════════════════╝ + `); + }); + + // Start schedulers + startSchedulers(); + await paymentMonitor.start(); + + console.log('✓ Schedulers started'); + console.log('\n🚀 Lightning Lottery API is ready!\n'); + + } catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +} + +// Handle graceful shutdown +process.on('SIGTERM', async () => { + console.log('SIGTERM received, shutting down gracefully...'); + paymentMonitor.stop(); + await db.close(); + process.exit(0); +}); + +process.on('SIGINT', async () => { + console.log('SIGINT received, shutting down gracefully...'); + paymentMonitor.stop(); + await db.close(); + process.exit(0); +}); + +// Start the server +start(); + diff --git a/back_end/src/middleware/auth.ts b/back_end/src/middleware/auth.ts new file mode 100644 index 0000000..81f5997 --- /dev/null +++ b/back_end/src/middleware/auth.ts @@ -0,0 +1,86 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import config from '../config'; + +export interface AuthRequest extends Request { + user?: { + id: string; + nostr_pubkey: string; + }; +} + +/** + * Middleware to verify JWT token + */ +export const verifyToken = (req: AuthRequest, res: Response, next: NextFunction) => { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + version: '1.0', + error: 'UNAUTHORIZED', + message: 'Missing or invalid authorization header', + }); + } + + const token = authHeader.substring(7); + + try { + const decoded = jwt.verify(token, config.jwt.secret) as { + id: string; + nostr_pubkey: string; + }; + + req.user = decoded; + next(); + } catch (error) { + return res.status(401).json({ + version: '1.0', + error: 'INVALID_TOKEN', + message: 'Invalid or expired token', + }); + } +}; + +/** + * Middleware to verify admin API key + */ +export const verifyAdmin = (req: Request, res: Response, next: NextFunction) => { + const adminKey = req.headers['x-admin-key']; + + if (!adminKey || adminKey !== config.admin.apiKey) { + return res.status(403).json({ + version: '1.0', + error: 'FORBIDDEN', + message: 'Invalid admin API key', + }); + } + + next(); +}; + +/** + * Optional auth - doesn't fail if no token + */ +export const optionalAuth = (req: AuthRequest, res: Response, next: NextFunction) => { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next(); + } + + const token = authHeader.substring(7); + + try { + const decoded = jwt.verify(token, config.jwt.secret) as { + id: string; + nostr_pubkey: string; + }; + req.user = decoded; + } catch (error) { + // Token invalid but we don't fail + } + + next(); +}; + diff --git a/back_end/src/middleware/rateLimit.ts b/back_end/src/middleware/rateLimit.ts new file mode 100644 index 0000000..b1aebbe --- /dev/null +++ b/back_end/src/middleware/rateLimit.ts @@ -0,0 +1,61 @@ +import rateLimit from 'express-rate-limit'; + +/** + * Rate limiter for buy endpoint + * Max 10 calls per IP per minute + */ +export const buyRateLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 10, + message: { + version: '1.0', + error: 'RATE_LIMIT', + message: 'Too many purchase requests, please try again later', + retry_after: 60, + }, + standardHeaders: true, + legacyHeaders: false, + // Skip failed requests - don't count them against the limit + skipFailedRequests: true, + // Use IP from request, ignore X-Forwarded-For in development + validate: { xForwardedForHeader: false }, +}); + +/** + * Rate limiter for ticket status endpoint + * Max 60 calls per minute + */ +export const ticketStatusRateLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 60, + message: { + version: '1.0', + error: 'RATE_LIMIT', + message: 'Too many status requests, please try again later', + retry_after: 60, + }, + standardHeaders: true, + legacyHeaders: false, + skipFailedRequests: true, + validate: { xForwardedForHeader: false }, +}); + +/** + * General rate limiter + * Max 100 requests per minute + */ +export const generalRateLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 100, + message: { + version: '1.0', + error: 'RATE_LIMIT', + message: 'Too many requests, please try again later', + retry_after: 60, + }, + standardHeaders: true, + legacyHeaders: false, + skipFailedRequests: true, + validate: { xForwardedForHeader: false }, +}); + diff --git a/back_end/src/routes/admin.ts b/back_end/src/routes/admin.ts new file mode 100644 index 0000000..d1f1cea --- /dev/null +++ b/back_end/src/routes/admin.ts @@ -0,0 +1,130 @@ +import { Router } from 'express'; +import { + listCycles, + runDrawManually, + retryPayout, + listPayouts +} from '../controllers/admin'; +import { verifyAdmin } from '../middleware/auth'; + +const router = Router(); + +// All admin routes require admin key +router.use(verifyAdmin); + +/** + * @swagger + * /admin/cycles: + * get: + * summary: List all jackpot cycles + * tags: [Admin] + * security: + * - adminKey: [] + * parameters: + * - in: query + * name: status + * schema: + * type: string + * description: Filter by status + * - in: query + * name: cycle_type + * schema: + * type: string + * description: Filter by cycle type + * - in: query + * name: limit + * schema: + * type: integer + * default: 50 + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * responses: + * 200: + * description: List of cycles + * 403: + * description: Invalid admin key + */ +router.get('/cycles', listCycles); + +/** + * @swagger + * /admin/cycles/{id}/run-draw: + * post: + * summary: Manually trigger a draw for a cycle + * tags: [Admin] + * security: + * - adminKey: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * format: uuid + * responses: + * 200: + * description: Draw executed successfully + * 400: + * description: Draw failed or invalid cycle + * 403: + * description: Invalid admin key + */ +router.post('/cycles/:id/run-draw', runDrawManually); + +/** + * @swagger + * /admin/payouts: + * get: + * summary: List all payouts + * tags: [Admin] + * security: + * - adminKey: [] + * parameters: + * - in: query + * name: status + * schema: + * type: string + * description: Filter by status + * - in: query + * name: limit + * schema: + * type: integer + * default: 50 + * responses: + * 200: + * description: List of payouts + * 403: + * description: Invalid admin key + */ +router.get('/payouts', listPayouts); + +/** + * @swagger + * /admin/payouts/{id}/retry: + * post: + * summary: Retry a failed payout + * tags: [Admin] + * security: + * - adminKey: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * format: uuid + * responses: + * 200: + * description: Payout retry successful + * 400: + * description: Retry failed or invalid payout + * 403: + * description: Invalid admin key + */ +router.post('/payouts/:id/retry', retryPayout); + +export default router; + diff --git a/back_end/src/routes/public.ts b/back_end/src/routes/public.ts new file mode 100644 index 0000000..9cb4777 --- /dev/null +++ b/back_end/src/routes/public.ts @@ -0,0 +1,191 @@ +import { Router } from 'express'; +import { + getNextJackpot, + buyTickets, + getTicketStatus, + getPastWins +} from '../controllers/public'; +import { buyRateLimiter, ticketStatusRateLimiter } from '../middleware/rateLimit'; +import { optionalAuth } from '../middleware/auth'; + +const router = Router(); + +/** + * @swagger + * /jackpot/next: + * get: + * summary: Get next upcoming jackpot cycle + * tags: [Public] + * responses: + * 200: + * description: Next jackpot cycle information + * content: + * application/json: + * schema: + * type: object + * properties: + * version: + * type: string + * example: "1.0" + * data: + * type: object + * properties: + * lottery: + * $ref: '#/components/schemas/Lottery' + * cycle: + * $ref: '#/components/schemas/JackpotCycle' + * 503: + * description: No active lottery or cycle available + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ +router.get('/jackpot/next', getNextJackpot); + +/** + * @swagger + * /jackpot/buy: + * post: + * summary: Purchase lottery tickets + * tags: [Public] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - tickets + * - lightning_address + * properties: + * tickets: + * type: integer + * minimum: 1 + * maximum: 100 + * description: Number of tickets to purchase + * example: 5 + * lightning_address: + * type: string + * description: Lightning Address for receiving payouts + * example: "user@getalby.com" + * nostr_pubkey: + * type: string + * description: Optional Nostr public key + * example: "npub1..." + * responses: + * 200: + * description: Invoice created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * version: + * type: string + * data: + * type: object + * properties: + * ticket_purchase_id: + * type: string + * format: uuid + * public_url: + * type: string + * invoice: + * type: object + * properties: + * payment_request: + * type: string + * description: BOLT11 invoice + * amount_sats: + * type: integer + * 400: + * description: Invalid input + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ +router.post('/jackpot/buy', buyRateLimiter, optionalAuth, buyTickets); + +/** + * @swagger + * /jackpot/past-wins: + * get: + * summary: List recent jackpot winners + * tags: [Public] + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 20 + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * responses: + * 200: + * description: List of completed jackpots and their winners + * 500: + * description: Failed to load past wins + */ +router.get('/jackpot/past-wins', getPastWins); + +/** + * @swagger + * /tickets/{id}: + * get: + * summary: Get ticket purchase status + * tags: [Public] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * format: uuid + * description: Ticket purchase ID + * responses: + * 200: + * description: Ticket status retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * version: + * type: string + * data: + * type: object + * properties: + * purchase: + * $ref: '#/components/schemas/TicketPurchase' + * tickets: + * type: array + * items: + * $ref: '#/components/schemas/Ticket' + * cycle: + * $ref: '#/components/schemas/JackpotCycle' + * result: + * type: object + * properties: + * has_drawn: + * type: boolean + * is_winner: + * type: boolean + * payout: + * type: object + * nullable: true + * 404: + * description: Ticket purchase not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ +router.get('/tickets/:id', ticketStatusRateLimiter, getTicketStatus); + +export default router; + diff --git a/back_end/src/routes/user.ts b/back_end/src/routes/user.ts new file mode 100644 index 0000000..5edfcab --- /dev/null +++ b/back_end/src/routes/user.ts @@ -0,0 +1,152 @@ +import { Router } from 'express'; +import { + nostrAuth, + getProfile, + updateLightningAddress, + getUserTickets, + getUserWins +} from '../controllers/user'; +import { verifyToken } from '../middleware/auth'; + +const router = Router(); + +/** + * @swagger + * /auth/nostr: + * post: + * summary: Authenticate with Nostr + * tags: [User] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - nostr_pubkey + * - signed_message + * - nonce + * properties: + * nostr_pubkey: + * type: string + * description: Nostr public key (hex or npub) + * signed_message: + * type: string + * description: Signature of the nonce + * nonce: + * type: string + * description: Random nonce for signature verification + * responses: + * 200: + * description: Authentication successful + * content: + * application/json: + * schema: + * type: object + * properties: + * version: + * type: string + * data: + * type: object + * properties: + * token: + * type: string + * description: JWT token + * user: + * type: object + * 400: + * description: Invalid public key or signature + */ +router.post('/auth/nostr', nostrAuth); + +/** + * @swagger + * /me: + * get: + * summary: Get user profile + * tags: [User] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: User profile and statistics + * 401: + * description: Unauthorized + */ +router.get('/me', verifyToken, getProfile); + +/** + * @swagger + * /me/lightning-address: + * patch: + * summary: Update user's Lightning Address + * tags: [User] + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - lightning_address + * properties: + * lightning_address: + * type: string + * example: "user@getalby.com" + * responses: + * 200: + * description: Lightning Address updated + * 400: + * description: Invalid Lightning Address + * 401: + * description: Unauthorized + */ +router.patch('/me/lightning-address', verifyToken, updateLightningAddress); + +/** + * @swagger + * /me/tickets: + * get: + * summary: Get user's ticket purchases + * tags: [User] + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 50 + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * responses: + * 200: + * description: User's ticket purchase history + * 401: + * description: Unauthorized + */ +router.get('/me/tickets', verifyToken, getUserTickets); + +/** + * @swagger + * /me/wins: + * get: + * summary: Get user's wins and payouts + * tags: [User] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: User's wins + * 401: + * description: Unauthorized + */ +router.get('/me/wins', verifyToken, getUserWins); + +export default router; + diff --git a/back_end/src/routes/webhooks.ts b/back_end/src/routes/webhooks.ts new file mode 100644 index 0000000..61c52c2 --- /dev/null +++ b/back_end/src/routes/webhooks.ts @@ -0,0 +1,44 @@ +import { Router } from 'express'; +import { handleLNbitsPayment } from '../controllers/webhooks'; + +const router = Router(); + +/** + * @swagger + * /webhooks/lnbits/payment: + * post: + * summary: LNbits payment webhook callback + * description: LNbits calls this endpoint when a Lightning invoice is paid. + * tags: [Webhooks] + * parameters: + * - in: header + * name: X-Webhook-Secret + * schema: + * type: string + * description: Shared secret configured in LNbits (or supply `secret` query param) + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * payment_hash: + * type: string + * amount: + * type: number + * description: Amount paid in sats + * paid: + * type: boolean + * responses: + * 200: + * description: Webhook processed + * 403: + * description: Invalid secret + * 500: + * description: Internal error processing the webhook + */ +router.post('/lnbits/payment', handleLNbitsPayment); + +export default router; + diff --git a/back_end/src/scheduler/index.ts b/back_end/src/scheduler/index.ts new file mode 100644 index 0000000..4753099 --- /dev/null +++ b/back_end/src/scheduler/index.ts @@ -0,0 +1,199 @@ +import cron from 'node-cron'; +import { db } from '../database'; +import { executeDraw } from '../services/draw'; +import { autoRetryFailedPayouts } from '../services/payout'; +import config from '../config'; +import { JackpotCycle } from '../types'; + +/** + * Generate future cycles for all cycle types + */ +async function generateFutureCycles(): Promise { + try { + console.log('Running cycle generator...'); + + // Get active lottery + const lotteryResult = await db.query( + `SELECT * FROM lotteries WHERE status = 'active' LIMIT 1` + ); + + if (lotteryResult.rows.length === 0) { + console.log('No active lottery found'); + return; + } + + const lottery = lotteryResult.rows[0]; + const cycleTypes: Array<'hourly' | 'daily' | 'weekly' | 'monthly'> = ['hourly', 'daily']; + + for (const cycleType of cycleTypes) { + await generateCyclesForType(lottery.id, cycleType); + } + + } catch (error) { + console.error('Cycle generation error:', error); + } +} + +/** + * Generate cycles for a specific type + */ +async function generateCyclesForType( + lotteryId: string, + cycleType: 'hourly' | 'daily' | 'weekly' | 'monthly' +): Promise { + try { + // Determine horizon (how far in the future to generate) + const horizonHours = cycleType === 'hourly' ? 48 : 168; // 48h for hourly, 1 week for daily + const horizonDate = new Date(Date.now() + horizonHours * 60 * 60 * 1000); + + // Get latest cycle for this type + const latestResult = await db.query( + `SELECT * FROM jackpot_cycles + WHERE lottery_id = $1 AND cycle_type = $2 + ORDER BY sequence_number DESC + LIMIT 1`, + [lotteryId, cycleType] + ); + + let lastScheduledAt: Date; + let sequenceNumber: number; + + if (latestResult.rows.length === 0) { + // No cycles exist, start from now + lastScheduledAt = new Date(); + sequenceNumber = 0; + } else { + const latest = latestResult.rows[0]; + lastScheduledAt = new Date(latest.scheduled_at); + sequenceNumber = latest.sequence_number; + } + + // Generate cycles until horizon + while (lastScheduledAt < horizonDate) { + sequenceNumber++; + + // Calculate next scheduled time + let nextScheduledAt: Date; + + switch (cycleType) { + case 'hourly': + nextScheduledAt = new Date(lastScheduledAt.getTime() + 60 * 60 * 1000); + break; + case 'daily': + nextScheduledAt = new Date(lastScheduledAt.getTime() + 24 * 60 * 60 * 1000); + nextScheduledAt.setHours(20, 0, 0, 0); // 8 PM UTC + break; + case 'weekly': + nextScheduledAt = new Date(lastScheduledAt.getTime() + 7 * 24 * 60 * 60 * 1000); + break; + case 'monthly': + nextScheduledAt = new Date(lastScheduledAt); + nextScheduledAt.setMonth(nextScheduledAt.getMonth() + 1); + break; + } + + // Sales open immediately, close at draw time + const salesOpenAt = new Date(); + const salesCloseAt = nextScheduledAt; + + // Check if cycle already exists + const existingResult = await db.query( + `SELECT id FROM jackpot_cycles + WHERE lottery_id = $1 AND cycle_type = $2 AND sequence_number = $3`, + [lotteryId, cycleType, sequenceNumber] + ); + + if (existingResult.rows.length === 0) { + // Create new cycle with explicit UUID generation + const crypto = require('crypto'); + const cycleId = crypto.randomUUID(); + + await db.query( + `INSERT INTO jackpot_cycles ( + id, lottery_id, cycle_type, sequence_number, scheduled_at, + sales_open_at, sales_close_at, status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + [ + cycleId, + lotteryId, + cycleType, + sequenceNumber, + nextScheduledAt.toISOString(), + salesOpenAt.toISOString(), + salesCloseAt.toISOString(), + 'scheduled', + ] + ); + + console.log(`Created ${cycleType} cycle #${sequenceNumber} (${cycleId}) for ${nextScheduledAt.toISOString()}`); + } + + lastScheduledAt = nextScheduledAt; + } + + } catch (error) { + console.error(`Error generating ${cycleType} cycles:`, error); + } +} + +/** + * Check for cycles that need to be drawn + */ +async function checkAndExecuteDraws(): Promise { + try { + console.log('Checking for cycles to draw...'); + + const now = new Date(); + + // Find cycles that are ready to draw + const result = await db.query( + `SELECT * FROM jackpot_cycles + WHERE status IN ('scheduled', 'sales_open') + AND scheduled_at <= $1 + ORDER BY scheduled_at ASC + LIMIT 10`, + [now.toISOString()] + ); + + console.log(`Found ${result.rows.length} cycles ready for draw`); + + for (const cycle of result.rows) { + console.log(`Executing draw for cycle ${cycle.id} (${cycle.cycle_type})`); + await executeDraw(cycle.id); + } + + } catch (error) { + console.error('Draw execution scheduler error:', error); + } +} + +/** + * Start all schedulers + */ +export function startSchedulers(): void { + console.log('Starting schedulers...'); + + // Cycle generator - every 5 minutes (or configured interval) + const cycleGenInterval = Math.max(config.scheduler.cycleGeneratorIntervalSeconds, 60); + cron.schedule(`*/${Math.floor(cycleGenInterval / 60)} * * * *`, generateFutureCycles); + + console.log(`✓ Cycle generator scheduled (every ${cycleGenInterval}s)`); + + // Draw executor - every minute (or configured interval) + const drawInterval = Math.max(config.scheduler.drawIntervalSeconds, 30); + cron.schedule(`*/${Math.floor(drawInterval / 60)} * * * *`, checkAndExecuteDraws); + + console.log(`✓ Draw executor scheduled (every ${drawInterval}s)`); + + // Payout retry - every 10 minutes + cron.schedule('*/10 * * * *', autoRetryFailedPayouts); + + console.log(`✓ Payout retry scheduled (every 10 minutes)`); + + // Run immediately on startup + setTimeout(() => { + generateFutureCycles(); + checkAndExecuteDraws(); + }, 5000); +} + diff --git a/back_end/src/services/draw.ts b/back_end/src/services/draw.ts new file mode 100644 index 0000000..922cc73 --- /dev/null +++ b/back_end/src/services/draw.ts @@ -0,0 +1,443 @@ +import crypto from 'crypto'; +import { db } from '../database'; +import { lnbitsService } from './lnbits'; +import { JackpotCycle, Ticket, Payout } from '../types'; +import config from '../config'; + +interface DrawResult { + success: boolean; + error?: string; + message?: string; + winningTicketId?: string; + potAfterFeeSats?: number; + payoutStatus?: string; +} + +interface PayoutAttemptResult { + status: 'paid' | 'failed'; + retryCount?: number; +} + +const RANDOM_INDEX_BYTES = 8; + +function pickRandomIndex(length: number): number { + if (length <= 0) { + throw new Error('Cannot pick random index from empty list'); + } + const randomBytes = crypto.randomBytes(RANDOM_INDEX_BYTES); + const randomValue = BigInt('0x' + randomBytes.toString('hex')); + return Number(randomValue % BigInt(length)); +} + +async function attemptImmediatePayout( + client: { query: typeof db.query }, + payoutId: string, + lightningAddress: string, + amountSats: number +): Promise { + try { + const paymentResult = await lnbitsService.payLightningAddress( + lightningAddress, + amountSats + ); + + await client.query( + `UPDATE payouts + SET status = 'paid', lnbits_payment_id = $1, updated_at = NOW() + WHERE id = $2`, + [paymentResult.payment_hash, payoutId] + ); + + console.log('Payout successful:', { + payout_id: payoutId, + amount_sats: amountSats, + lightning_address: lightningAddress, + }); + + return { status: 'paid' }; + } catch (error: any) { + const updateResult = await client.query( + `UPDATE payouts + SET status = 'failed', + error_message = $1, + retry_count = retry_count + 1, + updated_at = NOW() + WHERE id = $2 + RETURNING retry_count`, + [error.message, payoutId] + ); + + const retryCount = updateResult.rows.length > 0 + ? parseInt(updateResult.rows[0].retry_count.toString()) + : undefined; + + console.error('Payout failed:', { + payout_id: payoutId, + error: error.message, + retry_count: retryCount, + }); + + return { status: 'failed', retryCount }; + } +} + +/** + * Execute draw for a specific cycle + */ +export async function executeDraw(cycleId: string): Promise { + const client = await db.getClient(); + + try { + await client.query('BEGIN'); + + // Lock the cycle row for update + const cycleResult = await client.query( + `SELECT * FROM jackpot_cycles WHERE id = $1 FOR UPDATE`, + [cycleId] + ); + + if (cycleResult.rows.length === 0) { + await client.query('ROLLBACK'); + return { + success: false, + error: 'CYCLE_NOT_FOUND', + message: 'Cycle not found', + }; + } + + const cycle = cycleResult.rows[0]; + + // Check if cycle can be drawn + if (cycle.status === 'completed') { + await client.query('ROLLBACK'); + return { + success: false, + error: 'ALREADY_COMPLETED', + message: 'Draw already completed for this cycle', + }; + } + + if (cycle.status === 'cancelled') { + await client.query('ROLLBACK'); + return { + success: false, + error: 'CYCLE_CANCELLED', + message: 'Cannot draw cancelled cycle', + }; + } + + // Check if it's time to draw + const now = new Date(); + if (now < cycle.scheduled_at) { + await client.query('ROLLBACK'); + return { + success: false, + error: 'TOO_EARLY', + message: 'Draw time has not arrived yet', + }; + } + + // Update status to drawing + await client.query( + `UPDATE jackpot_cycles SET status = 'drawing', updated_at = NOW() WHERE id = $1`, + [cycleId] + ); + + // Get all tickets for this cycle + const ticketsResult = await client.query( + `SELECT * FROM tickets WHERE cycle_id = $1 ORDER BY serial_number`, + [cycleId] + ); + + const tickets = ticketsResult.rows; + + // Handle case with no tickets + if (tickets.length === 0) { + await client.query( + `UPDATE jackpot_cycles + SET status = 'completed', pot_after_fee_sats = 0, updated_at = NOW() + WHERE id = $1`, + [cycleId] + ); + + // Log draw + const emptyDrawLogId = crypto.randomUUID(); + + await client.query( + `INSERT INTO draw_logs ( + id, cycle_id, number_of_tickets, pot_total_sats, fee_percent, + pot_after_fee_sats, rng_source + ) VALUES ($1, $2, $3, $4, $5, $6, $7)`, + [emptyDrawLogId, cycleId, 0, 0, 0, 0, 'crypto.randomInt'] + ); + + await client.query('COMMIT'); + + console.log('Draw completed with no tickets:', { cycle_id: cycleId }); + + return { + success: true, + message: 'Draw completed with no tickets', + potAfterFeeSats: 0, + }; + } + + // Get lottery for fee percent + const lotteryResult = await client.query( + `SELECT * FROM lotteries WHERE id = $1`, + [cycle.lottery_id] + ); + const lottery = lotteryResult.rows[0]; + + // Calculate pot after fee + const potTotalSats = parseInt(cycle.pot_total_sats.toString()); + const feePercent = lottery.fee_percent; + const potAfterFeeSats = Math.floor(potTotalSats * (100 - feePercent) / 100); + + // Select random winning ticket via cryptographic randomness + const randomIndex = pickRandomIndex(tickets.length); + const winningTicket = tickets[randomIndex]; + + console.log('Draw execution:', { + cycle_id: cycleId, + total_tickets: tickets.length, + pot_total_sats: potTotalSats, + pot_after_fee_sats: potAfterFeeSats, + random_index: randomIndex, + winning_ticket_id: winningTicket.id, + winning_serial: winningTicket.serial_number, + }); + + // Update cycle with winner + await client.query( + `UPDATE jackpot_cycles + SET pot_after_fee_sats = $1, + winning_ticket_id = $2, + winning_lightning_address = $3, + updated_at = NOW() + WHERE id = $4`, + [potAfterFeeSats, winningTicket.id, winningTicket.lightning_address, cycleId] + ); + + // Create payout record + const payoutId = crypto.randomUUID(); + + const payoutResult = await client.query( + `INSERT INTO payouts ( + id, lottery_id, cycle_id, ticket_id, user_id, lightning_address, + amount_sats, status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + payoutId, + cycle.lottery_id, + cycleId, + winningTicket.id, + winningTicket.user_id, + winningTicket.lightning_address, + potAfterFeeSats, + 'pending', + ] + ); + + const payout = payoutResult.rows[0]; + + // Log draw for audit + const drawLogId = crypto.randomUUID(); + + await client.query( + `INSERT INTO draw_logs ( + id, cycle_id, number_of_tickets, pot_total_sats, fee_percent, + pot_after_fee_sats, winner_ticket_id, winner_lightning_address, + rng_source, selected_index + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, + [ + drawLogId, + cycleId, + tickets.length, + potTotalSats, + feePercent, + potAfterFeeSats, + winningTicket.id, + winningTicket.lightning_address, + 'crypto.randomInt', + randomIndex, + ] + ); + + const payoutAttempt = await attemptImmediatePayout( + client, + payout.id, + winningTicket.lightning_address, + potAfterFeeSats + ); + const payoutStatus = payoutAttempt.status; + + // Mark cycle as completed + await client.query( + `UPDATE jackpot_cycles SET status = 'completed', updated_at = NOW() WHERE id = $1`, + [cycleId] + ); + + await client.query('COMMIT'); + + return { + success: true, + winningTicketId: winningTicket.id, + potAfterFeeSats, + payoutStatus, + }; + + } catch (error: any) { + await client.query('ROLLBACK'); + console.error('Draw execution error:', error); + return { + success: false, + error: 'DRAW_EXECUTION_ERROR', + message: error.message, + }; + } finally { + if (client.release) { + client.release(); + } + } +} + +export async function redrawWinner(cycleId: string): Promise { + const client = await db.getClient(); + + try { + await client.query('BEGIN'); + + const cycleResult = await client.query( + `SELECT * FROM jackpot_cycles WHERE id = $1`, + [cycleId] + ); + + if (cycleResult.rows.length === 0) { + await client.query('ROLLBACK'); + console.warn('Cannot redraw winner, cycle not found', { cycle_id: cycleId }); + return false; + } + + const cycle = cycleResult.rows[0]; + + const candidatesResult = await client.query( + `SELECT * FROM tickets + WHERE cycle_id = $1 + AND id NOT IN ( + SELECT ticket_id FROM payouts WHERE cycle_id = $2 + ) + ORDER BY serial_number`, + [cycleId, cycleId] + ); + + if (candidatesResult.rows.length === 0) { + await client.query('ROLLBACK'); + console.warn('No eligible tickets remain for redraw', { cycle_id: cycleId }); + return false; + } + + const tickets = candidatesResult.rows; + const randomIndex = pickRandomIndex(tickets.length); + const winningTicket = tickets[randomIndex]; + + let potAfterFeeSats = cycle.pot_after_fee_sats + ? parseInt(cycle.pot_after_fee_sats.toString()) + : null; + + const potTotalSats = parseInt(cycle.pot_total_sats.toString()); + + if (potAfterFeeSats === null) { + const lotteryResult = await client.query( + `SELECT fee_percent FROM lotteries WHERE id = $1`, + [cycle.lottery_id] + ); + + const feePercent = lotteryResult.rows.length > 0 + ? lotteryResult.rows[0].fee_percent + : config.lottery.defaultHouseFeePercent; + + potAfterFeeSats = Math.floor(potTotalSats * (100 - feePercent) / 100); + } + + await client.query( + `UPDATE jackpot_cycles + SET winning_ticket_id = $1, + winning_lightning_address = $2, + pot_after_fee_sats = $3, + updated_at = NOW() + WHERE id = $4`, + [winningTicket.id, winningTicket.lightning_address, potAfterFeeSats, cycleId] + ); + + const payoutId = crypto.randomUUID(); + + const payoutResult = await client.query( + `INSERT INTO payouts ( + id, lottery_id, cycle_id, ticket_id, user_id, lightning_address, + amount_sats, status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + payoutId, + cycle.lottery_id, + cycleId, + winningTicket.id, + winningTicket.user_id, + winningTicket.lightning_address, + potAfterFeeSats, + 'pending', + ] + ); + + const payout = payoutResult.rows[0]; + + const drawLogId = crypto.randomUUID(); + + await client.query( + `INSERT INTO draw_logs ( + id, cycle_id, number_of_tickets, pot_total_sats, + pot_after_fee_sats, winner_ticket_id, winner_lightning_address, + rng_source, selected_index + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, + [ + drawLogId, + cycleId, + tickets.length, + potTotalSats, + potAfterFeeSats, + winningTicket.id, + winningTicket.lightning_address, + 'redraw', + randomIndex, + ] + ); + + const attempt = await attemptImmediatePayout( + client, + payout.id, + winningTicket.lightning_address, + potAfterFeeSats + ); + + await client.query('COMMIT'); + + console.log('Winner redrawn for cycle', { + cycle_id: cycleId, + new_winner_ticket_id: winningTicket.id, + payout_status: attempt.status, + }); + + return true; + } catch (error) { + await client.query('ROLLBACK'); + console.error('Redraw winner error:', error); + return false; + } finally { + if (client.release) { + client.release(); + } + } +} + diff --git a/back_end/src/services/lnbits.ts b/back_end/src/services/lnbits.ts new file mode 100644 index 0000000..587b792 --- /dev/null +++ b/back_end/src/services/lnbits.ts @@ -0,0 +1,156 @@ +import axios, { AxiosInstance } from 'axios'; +import config from '../config'; + +interface CreateInvoiceParams { + amount: number; // sats + memo: string; + webhook?: string; +} + +interface Invoice { + payment_hash: string; + payment_request: string; + checking_id: string; +} + +interface PayoutParams { + bolt11?: string; + description?: string; + out?: boolean; +} + +interface PaymentResult { + payment_hash: string; + checking_id: string; + paid?: boolean; +} + +class LNbitsService { + private client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: config.lnbits.apiBaseUrl, + headers: { + 'X-Api-Key': config.lnbits.adminKey, + 'Content-Type': 'application/json', + }, + timeout: 30000, + }); + } + + /** + * Create a Lightning invoice + */ + async createInvoice(params: CreateInvoiceParams): Promise { + try { + const response = await this.client.post('/api/v1/payments', { + out: false, + amount: params.amount, + memo: params.memo, + webhook: params.webhook, + unit: 'sat', + }); + + return { + payment_hash: response.data.payment_hash, + payment_request: response.data.payment_request, + checking_id: response.data.checking_id, + }; + } catch (error: any) { + console.error('LNbits create invoice error:', error.response?.data || error.message); + throw new Error(`Failed to create invoice: ${error.response?.data?.detail || error.message}`); + } + } + + /** + * Pay a Lightning Address or BOLT11 invoice + */ + async payLightningAddress(lightningAddress: string, amountSats: number): Promise { + try { + // First resolve the Lightning Address to get LNURL + const [username, domain] = lightningAddress.split('@'); + if (!username || !domain) { + throw new Error('Invalid Lightning Address format'); + } + + // Fetch LNURL pay metadata + const lnurlResponse = await axios.get( + `https://${domain}/.well-known/lnurlp/${username}` + ); + + const { callback, minSendable, maxSendable } = lnurlResponse.data; + + const amountMsats = amountSats * 1000; + + if (amountMsats < minSendable || amountMsats > maxSendable) { + throw new Error('Amount out of range for this Lightning Address'); + } + + // Get invoice from callback + const invoiceResponse = await axios.get(callback, { + params: { amount: amountMsats }, + }); + + const bolt11 = invoiceResponse.data.pr; + + if (!bolt11) { + throw new Error('Failed to get invoice from Lightning Address'); + } + + // Pay the invoice using LNbits + const paymentResponse = await this.client.post('/api/v1/payments', { + out: true, + bolt11: bolt11, + }); + + return { + payment_hash: paymentResponse.data.payment_hash, + checking_id: paymentResponse.data.checking_id, + paid: true, + }; + } catch (error: any) { + console.error('LNbits payout error:', error.response?.data || error.message); + throw new Error(`Failed to pay Lightning Address: ${error.response?.data?.detail || error.message}`); + } + } + + /** + * Check payment status + */ + async checkPaymentStatus(paymentHash: string): Promise<{ paid: boolean }> { + try { + const response = await this.client.get(`/api/v1/payments/${paymentHash}`); + return { + paid: response.data.paid || false, + }; + } catch (error: any) { + console.error('LNbits check payment error:', error.response?.data || error.message); + throw new Error(`Failed to check payment status: ${error.message}`); + } + } + + /** + * Verify webhook signature/secret + */ + verifyWebhook(secret: string): boolean { + return secret === config.lnbits.webhookSecret; + } + + /** + * Health check for LNbits connectivity + */ + async healthCheck(): Promise { + try { + await this.client.get('/api/v1/wallet'); + return true; + } catch (error) { + console.error('LNbits health check failed:', error); + return false; + } + } +} + +export const lnbitsService = new LNbitsService(); +export default lnbitsService; + diff --git a/back_end/src/services/paymentMonitor.ts b/back_end/src/services/paymentMonitor.ts new file mode 100644 index 0000000..d0414eb --- /dev/null +++ b/back_end/src/services/paymentMonitor.ts @@ -0,0 +1,130 @@ +import { db } from '../database'; +import { lnbitsService } from './lnbits'; +import { finalizeTicketPurchase } from './paymentProcessor'; + +interface PendingInvoice { + purchaseId: string; + paymentHash: string; + addedAt: number; +} + +const POLL_INTERVAL_MS = 3000; +const EXPIRY_MS = 20 * 60 * 1000; + +class PaymentMonitor { + private pendingInvoices = new Map(); + private checkInterval: NodeJS.Timeout | null = null; + private isChecking = false; + + async start(): Promise { + if (this.checkInterval) { + console.log('Payment monitor already running'); + return; + } + + await this.bootstrapPendingInvoices(); + console.log('✓ Payment monitor started (checking every 3 seconds)'); + + // Immediate check, then schedule interval + this.checkPendingPayments(); + this.checkInterval = setInterval(() => { + this.checkPendingPayments(); + }, POLL_INTERVAL_MS); + } + + stop(): void { + if (this.checkInterval) { + clearInterval(this.checkInterval); + this.checkInterval = null; + console.log('Payment monitor stopped'); + } + } + + addInvoice(purchaseId: string, paymentHash?: string): void { + if (!purchaseId || !paymentHash) { + return; + } + + this.pendingInvoices.set(purchaseId, { + purchaseId, + paymentHash, + addedAt: Date.now(), + }); + + console.log(`📋 Monitoring payment: ${purchaseId} (${this.pendingInvoices.size} pending)`); + } + + removeInvoice(purchaseId: string): void { + this.pendingInvoices.delete(purchaseId); + } + + getStats() { + return { + pending: this.pendingInvoices.size, + checking: this.isChecking, + }; + } + + private async bootstrapPendingInvoices(): Promise { + const pendingResult = await db.query<{ id: string; lnbits_payment_hash: string }>( + `SELECT id, lnbits_payment_hash + FROM ticket_purchases + WHERE invoice_status = 'pending' + AND lnbits_payment_hash IS NOT NULL + AND lnbits_payment_hash <> ''` + ); + + pendingResult.rows.forEach(row => { + this.pendingInvoices.set(row.id, { + purchaseId: row.id, + paymentHash: row.lnbits_payment_hash, + addedAt: Date.now(), + }); + }); + + if (pendingResult.rows.length > 0) { + console.log(`📎 Restored ${pendingResult.rows.length} pending invoice(s) into monitor`); + } + } + + private async checkPendingPayments(): Promise { + if (this.isChecking || this.pendingInvoices.size === 0) { + return; + } + + this.isChecking = true; + + try { + const now = Date.now(); + const invoices = Array.from(this.pendingInvoices.values()); + + for (const invoice of invoices) { + if (now - invoice.addedAt > EXPIRY_MS) { + console.log(`⏰ Invoice expired: ${invoice.purchaseId}`); + this.pendingInvoices.delete(invoice.purchaseId); + continue; + } + + try { + const status = await lnbitsService.checkPaymentStatus(invoice.paymentHash); + if (status.paid) { + console.log(`💰 Payment detected via monitor: ${invoice.purchaseId}`); + await finalizeTicketPurchase(invoice.purchaseId); + this.pendingInvoices.delete(invoice.purchaseId); + } + } catch (error: any) { + console.error(`Error checking payment ${invoice.purchaseId}:`, error?.message || error); + } + } + } catch (error) { + console.error('Payment monitor error:', error); + } finally { + this.isChecking = false; + } + } +} + +export const paymentMonitor = new PaymentMonitor(); +export default paymentMonitor; + + diff --git a/back_end/src/services/paymentProcessor.ts b/back_end/src/services/paymentProcessor.ts new file mode 100644 index 0000000..4d72d2b --- /dev/null +++ b/back_end/src/services/paymentProcessor.ts @@ -0,0 +1,129 @@ +import crypto from 'crypto'; +import { db } from '../database'; +import { TicketPurchase } from '../types'; + +export type FinalizeStatus = 'success' | 'already_paid' | 'not_found'; + +export interface FinalizeResult { + status: FinalizeStatus; + purchaseId?: string; + ticketsIssued?: number; +} + +const SERIAL_MIN = 1; +const SERIAL_MAX = 1_000_000_000; // 1 billion possible ticket numbers + +function createSerialNumberGenerator(existingSerials: Set) { + return () => { + for (let attempts = 0; attempts < 2000; attempts++) { + const candidate = crypto.randomInt(SERIAL_MIN, SERIAL_MAX); + if (!existingSerials.has(candidate)) { + existingSerials.add(candidate); + return candidate; + } + } + throw new Error('Unable to generate unique ticket number'); + }; +} + +export async function finalizeTicketPurchase(purchaseId: string): Promise { + const client = await db.getClient(); + + try { + await client.query('BEGIN'); + + const purchaseResult = await client.query( + `SELECT * FROM ticket_purchases WHERE id = $1`, + [purchaseId] + ); + + if (purchaseResult.rows.length === 0) { + await client.query('ROLLBACK'); + return { status: 'not_found' }; + } + + const purchase = purchaseResult.rows[0]; + + if (purchase.invoice_status === 'paid') { + await client.query('ROLLBACK'); + return { status: 'already_paid', purchaseId: purchase.id, ticketsIssued: purchase.number_of_tickets }; + } + + await client.query( + `UPDATE ticket_purchases + SET invoice_status = 'paid', updated_at = NOW() + WHERE id = $1`, + [purchase.id] + ); + + let ticketsIssued = 0; + + if (purchase.ticket_issue_status === 'not_issued') { + const existingSerialsResult = await client.query( + `SELECT serial_number FROM tickets WHERE cycle_id = $1`, + [purchase.cycle_id] + ); + + const usedSerials = new Set( + existingSerialsResult.rows.map((row: any) => + parseInt(row.serial_number?.toString() ?? '0', 10) + ) + ); + + const generateSerialNumber = createSerialNumberGenerator(usedSerials); + + for (let i = 0; i < purchase.number_of_tickets; i++) { + const serialNumber = generateSerialNumber(); + ticketsIssued++; + + await client.query( + `INSERT INTO tickets ( + id, lottery_id, cycle_id, ticket_purchase_id, user_id, + lightning_address, serial_number + ) VALUES ($1, $2, $3, $4, $5, $6, $7)`, + [ + crypto.randomUUID(), + purchase.lottery_id, + purchase.cycle_id, + purchase.id, + purchase.user_id, + purchase.lightning_address, + serialNumber, + ] + ); + } + + await client.query( + `UPDATE ticket_purchases + SET ticket_issue_status = 'issued', updated_at = NOW() + WHERE id = $1`, + [purchase.id] + ); + + await client.query( + `UPDATE jackpot_cycles + SET pot_total_sats = pot_total_sats + $1, updated_at = NOW() + WHERE id = $2`, + [purchase.amount_sats, purchase.cycle_id] + ); + } + + await client.query('COMMIT'); + + return { + status: 'success', + purchaseId: purchase.id, + ticketsIssued: ticketsIssued || purchase.number_of_tickets, + }; + + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + if (client.release) { + client.release(); + } + } +} + + diff --git a/back_end/src/services/payout.ts b/back_end/src/services/payout.ts new file mode 100644 index 0000000..a64c0d5 --- /dev/null +++ b/back_end/src/services/payout.ts @@ -0,0 +1,176 @@ +import { db } from '../database'; +import { lnbitsService } from './lnbits'; +import { Payout } from '../types'; +import config from '../config'; +import { redrawWinner } from './draw'; + +interface PayoutRetryResult { + success: boolean; + error?: string; + message?: string; + status?: string; + retryCount?: number; +} + +const MAX_RETRY_ATTEMPTS = config.payout.maxAttemptsBeforeRedraw; + +/** + * Retry a failed payout + */ +export async function retryPayoutService(payoutId: string): Promise { + try { + // Get payout + const payoutResult = await db.query( + `SELECT * FROM payouts WHERE id = $1`, + [payoutId] + ); + + if (payoutResult.rows.length === 0) { + return { + success: false, + error: 'PAYOUT_NOT_FOUND', + message: 'Payout not found', + }; + } + + const payout = payoutResult.rows[0]; + + // Check if payout is in failed status + if (payout.status !== 'failed') { + return { + success: false, + error: 'INVALID_STATUS', + message: `Cannot retry payout with status: ${payout.status}`, + }; + } + + // Check retry limit + if (payout.retry_count >= MAX_RETRY_ATTEMPTS) { + return { + success: false, + error: 'MAX_RETRIES_EXCEEDED', + message: `Maximum retry attempts (${MAX_RETRY_ATTEMPTS}) exceeded`, + }; + } + + const amountSats = parseInt(payout.amount_sats.toString()); + + console.log('Retrying payout:', { + payout_id: payoutId, + attempt: payout.retry_count + 1, + amount_sats: amountSats, + lightning_address: payout.lightning_address, + }); + + // Attempt payout + try { + const paymentResult = await lnbitsService.payLightningAddress( + payout.lightning_address, + amountSats + ); + + // Update payout as paid + await db.query( + `UPDATE payouts + SET status = 'paid', + lnbits_payment_id = $1, + retry_count = retry_count + 1, + updated_at = NOW() + WHERE id = $2`, + [paymentResult.payment_hash, payoutId] + ); + + console.log('Payout retry successful:', { + payout_id: payoutId, + payment_hash: paymentResult.payment_hash, + }); + + return { + success: true, + status: 'paid', + retryCount: payout.retry_count + 1, + }; + + } catch (payoutError: any) { + // Update error message and increment retry count + const updateResult = await db.query( + `UPDATE payouts + SET error_message = $1, + retry_count = retry_count + 1, + updated_at = NOW() + WHERE id = $2 + RETURNING retry_count`, + [payoutError.message, payoutId] + ); + + const newRetryCount = updateResult.rows.length > 0 + ? parseInt(updateResult.rows[0].retry_count.toString()) + : payout.retry_count + 1; + + console.error('Payout retry failed:', { + payout_id: payoutId, + error: payoutError.message, + retry_count: newRetryCount, + }); + + if (newRetryCount >= MAX_RETRY_ATTEMPTS) { + console.warn('Max payout attempts reached. Drawing new winner.', { + cycle_id: payout.cycle_id, + payout_id: payoutId, + }); + await redrawWinner(payout.cycle_id); + } + + return { + success: false, + error: 'PAYOUT_FAILED', + message: payoutError.message, + status: 'failed', + retryCount: newRetryCount, + }; + } + + } catch (error: any) { + console.error('Retry payout error:', error); + return { + success: false, + error: 'INTERNAL_ERROR', + message: error.message, + }; + } +} + +/** + * Automatically retry all failed payouts that haven't exceeded max retries + */ +export async function autoRetryFailedPayouts(): Promise { + try { + const result = await db.query( + `SELECT * FROM payouts + WHERE status = 'failed' + AND retry_count < $1 + ORDER BY created_at ASC + LIMIT 10`, + [MAX_RETRY_ATTEMPTS] + ); + + console.log(`Found ${result.rows.length} failed payouts to retry`); + + for (const payout of result.rows) { + // Add delay between retries (exponential backoff) + const delaySeconds = Math.pow(2, payout.retry_count) * 10; + const timeSinceLastUpdate = Date.now() - payout.updated_at.getTime(); + + if (timeSinceLastUpdate < delaySeconds * 1000) { + console.log(`Skipping payout ${payout.id} - waiting for backoff period`); + continue; + } + + await retryPayoutService(payout.id); + } + + } catch (error) { + console.error('Auto retry failed payouts error:', error); + } +} + diff --git a/back_end/src/types/index.ts b/back_end/src/types/index.ts new file mode 100644 index 0000000..138c389 --- /dev/null +++ b/back_end/src/types/index.ts @@ -0,0 +1,108 @@ +export interface Lottery { + id: string; + name: string; + status: 'active' | 'paused' | 'finished'; + ticket_price_sats: number; + fee_percent: number; + created_at: Date; + updated_at: Date; +} + +export interface JackpotCycle { + id: string; + lottery_id: string; + cycle_type: 'hourly' | 'daily' | 'weekly' | 'monthly'; + sequence_number: number; + scheduled_at: Date; + sales_open_at: Date; + sales_close_at: Date; + status: 'scheduled' | 'sales_open' | 'drawing' | 'completed' | 'cancelled'; + pot_total_sats: number; + pot_after_fee_sats: number | null; + winning_ticket_id: string | null; + winning_lightning_address: string | null; + created_at: Date; + updated_at: Date; +} + +export interface TicketPurchase { + id: string; + lottery_id: string; + cycle_id: string; + user_id: string | null; + nostr_pubkey: string | null; + lightning_address: string; + buyer_name: string; + number_of_tickets: number; + ticket_price_sats: number; + amount_sats: number; + lnbits_invoice_id: string; + lnbits_payment_hash: string; + invoice_status: 'pending' | 'paid' | 'expired' | 'cancelled'; + ticket_issue_status: 'not_issued' | 'issued'; + created_at: Date; + updated_at: Date; +} + +export interface Ticket { + id: string; + lottery_id: string; + cycle_id: string; + ticket_purchase_id: string; + user_id: string | null; + lightning_address: string; + serial_number: number; + created_at: Date; +} + +export interface Payout { + id: string; + lottery_id: string; + cycle_id: string; + ticket_id: string; + user_id: string | null; + lightning_address: string; + amount_sats: number; + status: 'pending' | 'paid' | 'failed'; + lnbits_payment_id: string | null; + error_message: string | null; + retry_count: number; + created_at: Date; + updated_at: Date; +} + +export interface User { + id: string; + nostr_pubkey: string; + display_name: string | null; + lightning_address: string | null; + created_at: Date; + updated_at: Date; +} + +export interface DrawLog { + id: string; + cycle_id: string; + number_of_tickets: number; + pot_total_sats: number; + fee_percent: number; + pot_after_fee_sats: number; + winner_ticket_id: string | null; + winner_lightning_address: string | null; + rng_source: string; + selected_index: number | null; + created_at: Date; +} + +export interface ApiError { + error: string; + message: string; +} + +export interface ApiResponse { + version: string; + data?: T; + error?: string; + message?: string; +} + diff --git a/back_end/src/utils/validation.ts b/back_end/src/utils/validation.ts new file mode 100644 index 0000000..e793f99 --- /dev/null +++ b/back_end/src/utils/validation.ts @@ -0,0 +1,70 @@ +/** + * Validates a Lightning Address format + */ +export function validateLightningAddress(address: string): boolean { + if (!address || typeof address !== 'string') { + return false; + } + + // Must contain @ symbol + if (!address.includes('@')) { + return false; + } + + // Basic email-like format check + const parts = address.split('@'); + if (parts.length !== 2) { + return false; + } + + const [username, domain] = parts; + + // Username and domain must not be empty + if (!username || !domain) { + return false; + } + + // Length check + if (address.length > 255) { + return false; + } + + return true; +} + +/** + * Validates Nostr public key (hex or npub format) + */ +export function validateNostrPubkey(pubkey: string): boolean { + if (!pubkey || typeof pubkey !== 'string') { + return false; + } + + // Hex format: 64 characters + if (/^[0-9a-f]{64}$/i.test(pubkey)) { + return true; + } + + // npub format (bech32) + if (pubkey.startsWith('npub1') && pubkey.length === 63) { + return true; + } + + return false; +} + +/** + * Validates number of tickets + */ +export function validateTicketCount(count: number, max: number = 100): boolean { + return Number.isInteger(count) && count > 0 && count <= max; +} + +/** + * Sanitizes input string + */ +export function sanitizeString(input: string, maxLength: number = 255): string { + if (!input) return ''; + return input.trim().substring(0, maxLength); +} + diff --git a/back_end/tsconfig.json b/back_end/tsconfig.json new file mode 100644 index 0000000..ab4b79f --- /dev/null +++ b/back_end/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..203f9e5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,75 @@ +version: '3.8' + +services: + postgres: + image: postgres:14-alpine + container_name: lightning-lotto-db + environment: + POSTGRES_DB: lightning_lotto + POSTGRES_USER: lottery_user + POSTGRES_PASSWORD: lottery_password + volumes: + - postgres_data:/var/lib/postgresql/data + - ./back_end/src/database/schema.sql:/docker-entrypoint-initdb.d/schema.sql + ports: + - "5432:5432" + networks: + - lightning-lotto-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U lottery_user -d lightning_lotto"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: + context: ./back_end + dockerfile: Dockerfile + container_name: lightning-lotto-backend + depends_on: + postgres: + condition: service_healthy + environment: + APP_PORT: 3000 + APP_BASE_URL: ${APP_BASE_URL:-http://localhost:3000} + FRONTEND_BASE_URL: ${FRONTEND_BASE_URL:-http://localhost:3001} + NODE_ENV: production + DATABASE_URL: postgresql://lottery_user:lottery_password@postgres:5432/lightning_lotto + LNBITS_API_BASE_URL: ${LNBITS_API_BASE_URL} + LNBITS_ADMIN_KEY: ${LNBITS_ADMIN_KEY} + LNBITS_WEBHOOK_SECRET: ${LNBITS_WEBHOOK_SECRET} + JWT_SECRET: ${JWT_SECRET} + ADMIN_API_KEY: ${ADMIN_API_KEY} + DEFAULT_TICKET_PRICE_SATS: ${DEFAULT_TICKET_PRICE_SATS:-1000} + DEFAULT_HOUSE_FEE_PERCENT: ${DEFAULT_HOUSE_FEE_PERCENT:-5} + DRAW_SCHEDULER_INTERVAL_SECONDS: 60 + CYCLE_GENERATOR_INTERVAL_SECONDS: 300 + ports: + - "3000:3000" + networks: + - lightning-lotto-network + restart: unless-stopped + + frontend: + build: + context: ./front_end + dockerfile: Dockerfile + container_name: lightning-lotto-frontend + depends_on: + - backend + environment: + NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:3000} + NEXT_PUBLIC_APP_BASE_URL: ${NEXT_PUBLIC_APP_BASE_URL:-http://localhost:3001} + ports: + - "3001:3000" + networks: + - lightning-lotto-network + restart: unless-stopped + +volumes: + postgres_data: + +networks: + lightning-lotto-network: + driver: bridge + diff --git a/front_end/.gitignore b/front_end/.gitignore new file mode 100644 index 0000000..8ac666e --- /dev/null +++ b/front_end/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + diff --git a/front_end/Dockerfile b/front_end/Dockerfile new file mode 100644 index 0000000..ad06063 --- /dev/null +++ b/front_end/Dockerfile @@ -0,0 +1,31 @@ +FROM node:18-alpine AS builder + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci + +# Copy source +COPY . . + +# Build +RUN npm run build + +# Production image +FROM node:18-alpine + +WORKDIR /app + +ENV NODE_ENV=production + +# Copy built app +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +EXPOSE 3000 + +CMD ["npm", "start"] + diff --git a/front_end/README.md b/front_end/README.md new file mode 100644 index 0000000..3b6595c --- /dev/null +++ b/front_end/README.md @@ -0,0 +1,106 @@ +# Lightning Lottery Frontend + +Next.js-based frontend for the Lightning Lottery system. + +## Features + +- **Responsive Design**: Mobile-first, works on all devices +- **Real-time Updates**: Live countdown and automatic status polling +- **Lightning Payments**: QR code invoice display +- **Nostr Authentication**: Optional user login via NIP-07 +- **User Dashboard**: View tickets, wins, and statistics + +## Tech Stack + +- **Framework**: Next.js 14 (App Router) +- **Language**: TypeScript +- **Styling**: TailwindCSS +- **State Management**: Redux Toolkit +- **QR Codes**: qrcode.react + +## Setup + +### Prerequisites + +- Node.js 18+ +- Backend API running + +### Installation + +1. Install dependencies: +```bash +npm install +``` + +2. Configure environment: +```bash +cp .env.example .env.local +# Edit .env.local with your configuration +``` + +3. Run development server: +```bash +npm run dev +``` + +Open [http://localhost:3001](http://localhost:3001) + +### Production Build + +```bash +npm run build +npm start +``` + +## Environment Variables + +- `NEXT_PUBLIC_API_BASE_URL` - Backend API URL +- `NEXT_PUBLIC_APP_BASE_URL` - Frontend public URL + +## Pages + +- `/` - Home page with current jackpot +- `/buy` - Buy lottery tickets +- `/tickets/[id]` - Ticket status page (public) +- `/dashboard` - User dashboard (Nostr auth required) +- `/dashboard/tickets` - User's ticket history +- `/dashboard/wins` - User's wins + +## Components + +### Reusable Components + +- `TopBar` - Navigation header +- `Footer` - Site footer +- `JackpotCountdown` - Live countdown timer +- `JackpotPotDisplay` - Pot amount display +- `LightningInvoiceCard` - Invoice QR code and copy +- `TicketList` - Display ticket serial numbers +- `PayoutStatus` - Payout status indicator +- `NostrLoginButton` - Nostr authentication + +## Deployment + +### Vercel + +```bash +vercel +``` + +### Netlify + +```bash +netlify deploy --prod +``` + +### Docker + +```bash +docker build -t lightning-lotto-frontend . +docker run -p 3001:3000 lightning-lotto-frontend +``` + +## License + +MIT + diff --git a/front_end/env.example b/front_end/env.example new file mode 100644 index 0000000..a02e2bd --- /dev/null +++ b/front_end/env.example @@ -0,0 +1,40 @@ +# Lightning Lottery Frontend - Environment Configuration +# Copy this file to .env.local and fill in your values + +# ====================== +# Server Configuration +# ====================== +# Port for Next.js development server (default: 3001) +PORT=3001 + +# ====================== +# API Configuration +# ====================== +# Backend API base URL +# Development: http://localhost:3000 +# Production: https://api.yourdomain.com +NEXT_PUBLIC_API_BASE_URL=http://localhost:3000 + +# Frontend application base URL +# Development: http://localhost:3001 +# Production: https://yourdomain.com +NEXT_PUBLIC_APP_BASE_URL=http://localhost:3001 + +# Backend API fallback port used when the frontend is accessed via a non-localhost host +# Keep at 3000 if you run the API on the default dev port +NEXT_PUBLIC_BACKEND_PORT=3000 + +# ====================== +# Optional: Admin Configuration +# ====================== +# Only needed if you want to use admin features from the frontend +# NEXT_PUBLIC_ADMIN_KEY=your_admin_api_key_here + +# ====================== +# Notes +# ====================== +# - Copy this file to .env.local for local development +# - All variables that need to be accessible in the browser must start with NEXT_PUBLIC_ +# - Never expose sensitive keys in production frontend +# - PORT is read by the npm scripts to set the dev server port +# - Restart the dev server after changing .env.local diff --git a/front_end/next.config.js b/front_end/next.config.js new file mode 100644 index 0000000..6e33c0e --- /dev/null +++ b/front_end/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + env: { + NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL || '', + NEXT_PUBLIC_APP_BASE_URL: process.env.NEXT_PUBLIC_APP_BASE_URL || '', + NEXT_PUBLIC_BACKEND_PORT: process.env.NEXT_PUBLIC_BACKEND_PORT || '', + }, +} + +module.exports = nextConfig + diff --git a/front_end/package-lock.json b/front_end/package-lock.json new file mode 100644 index 0000000..8617be3 --- /dev/null +++ b/front_end/package-lock.json @@ -0,0 +1,6833 @@ +{ + "name": "lightning-lotto-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lightning-lotto-frontend", + "version": "1.0.0", + "dependencies": { + "@reduxjs/toolkit": "^2.0.1", + "axios": "^1.6.2", + "next": "14.0.4", + "qrcode.react": "^3.1.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-redux": "^9.0.4" + }, + "devDependencies": { + "@types/node": "20.10.5", + "@types/react": "18.2.45", + "@types/react-dom": "18.2.18", + "autoprefixer": "^10.4.16", + "eslint": "8.56.0", + "eslint-config-next": "14.0.4", + "postcss": "^8.4.32", + "puppeteer": "^24.31.0", + "tailwindcss": "^3.3.6", + "typescript": "5.3.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", + "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.4.tgz", + "integrity": "sha512-U3qMNHmEZoVmHA0j/57nRfi3AscXNvkOnxDmle/69Jz/G0o/gWjXTDdlgILZdrxQ0Lw/jv2mPW8PGy0EGIHXhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", + "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", + "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", + "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", + "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", + "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", + "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", + "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", + "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", + "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.13.tgz", + "integrity": "sha512-a9Ruw3j3qlnB5a/zHRTkruppynxqaeE4H9WNj5eYGRWqw0ZauZ23f4W2ARf3hghF5doozyD+CRtt7XSYuYRI/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.3", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.0.tgz", + "integrity": "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.2.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", + "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", + "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.1.tgz", + "integrity": "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chromium-bidi": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-11.0.0.tgz", + "integrity": "sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1521046", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", + "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.261", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.261.tgz", + "integrity": "sha512-cmyHEWFqEt3ICUNF93ShneOF47DHoSDbLb7E/AonsWcbzg95N+kPXeLNfkdzgTT/vEUcoW76fxbLBkeYtfoM8A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.4.tgz", + "integrity": "sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "14.0.4", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.0.0.tgz", + "integrity": "sha512-XtRG4SINt4dpqlnJvs70O2j6hH7H0X8fUzFsjMn1rwnETaxwp83HLNimXBjZ78MrKl3/d3/pkzDH0o0Lkxm37Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", + "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", + "license": "MIT", + "dependencies": { + "@next/env": "14.0.4", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.0.4", + "@next/swc-darwin-x64": "14.0.4", + "@next/swc-linux-arm64-gnu": "14.0.4", + "@next/swc-linux-arm64-musl": "14.0.4", + "@next/swc-linux-x64-gnu": "14.0.4", + "@next/swc-linux-x64-musl": "14.0.4", + "@next/swc-win32-arm64-msvc": "14.0.4", + "@next/swc-win32-ia32-msvc": "14.0.4", + "@next/swc-win32-x64-msvc": "14.0.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "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 + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "24.31.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.31.0.tgz", + "integrity": "sha512-q8y5yLxLD8xdZdzNWqdOL43NbfvUOp60SYhaLZQwHC9CdKldxQKXOyJAciOr7oUJfyAH/KgB2wKvqT2sFKoVXA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.13", + "chromium-bidi": "11.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1521046", + "puppeteer-core": "24.31.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.31.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.31.0.tgz", + "integrity": "sha512-pnAohhSZipWQoFpXuGV7xCZfaGhqcBR9C4pVrU0QSrcMi7tQMH9J9lDBqBvyMAHQqe8HCARuREqFuVKRQOgTvg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.13", + "chromium-bidi": "11.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1521046", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.3.9", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "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 + } + } + }, + "node_modules/qrcode.react": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.2.0.tgz", + "integrity": "sha512-YietHHltOHA4+l5na1srdaMx4sVSOjV9tamHs+mwiLWAMr6QVACRUw1Neax5CptFILcNoITctJY0Ipyn5enQ8g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream/node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.9.tgz", + "integrity": "sha512-uIYvlRQ0PwtZR1EzHlTMol1G0lAlmOe6wPykF9a77AK3bkpvZHzIVxRE2ThOx5vjy2zISe0zhwf5rzuUfbo1PQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/front_end/package.json b/front_end/package.json new file mode 100644 index 0000000..d711860 --- /dev/null +++ b/front_end/package.json @@ -0,0 +1,32 @@ +{ + "name": "lightning-lotto-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev -p ${PORT:-3001}", + "build": "next build", + "start": "next start -p ${PORT:-3001}", + "lint": "next lint" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.0.1", + "axios": "^1.6.2", + "next": "14.0.4", + "qrcode.react": "^3.1.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-redux": "^9.0.4" + }, + "devDependencies": { + "@types/node": "20.10.5", + "@types/react": "18.2.45", + "@types/react-dom": "18.2.18", + "autoprefixer": "^10.4.16", + "eslint": "8.56.0", + "eslint-config-next": "14.0.4", + "postcss": "^8.4.32", + "puppeteer": "^24.31.0", + "tailwindcss": "^3.3.6", + "typescript": "5.3.3" + } +} diff --git a/front_end/postcss.config.js b/front_end/postcss.config.js new file mode 100644 index 0000000..2ce518b --- /dev/null +++ b/front_end/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + diff --git a/front_end/src/app/about/page.tsx b/front_end/src/app/about/page.tsx new file mode 100644 index 0000000..6904c55 --- /dev/null +++ b/front_end/src/app/about/page.tsx @@ -0,0 +1,190 @@ +'use client'; + +import Link from 'next/link'; +import STRINGS from '@/constants/strings'; + +export default function AboutPage() { + return ( +
+

+ About {STRINGS.app.title} +

+ +
+ {/* Introduction */} +
+

+ What is Lightning Lottery? +

+

+ Lightning Lottery is a provably fair lottery system built on the Bitcoin Lightning Network. + Players can purchase tickets using Lightning payments and winners receive instant payouts + directly to their Lightning address. No accounts required, no waiting for withdrawals. +

+
+ + {/* How It Works */} +
+

+ How It Works +

+
+
+
+ 1 +
+
+

Buy Tickets

+

+ Enter your Lightning address and pay the invoice. Each ticket gives you a unique + randomly-generated number for the draw. +

+
+
+
+
+ 2 +
+
+

Wait for the Draw

+

+ Draws happen on a regular schedule. Watch the countdown and see the pot grow + as more players join. +

+
+
+
+
+ 3 +
+
+

Win Instantly

+

+ When the draw happens, a winning ticket is selected using cryptographically secure + random number generation. The winner receives the pot automatically via Lightning! +

+
+
+
+
+ + {/* Fairness */} +
+

+ Provably Fair +

+
+

+ Our lottery uses cryptographically secure random number generation (CSPRNG) from + Node.js's crypto.randomBytes() module + to ensure completely unpredictable results. +

+
    +
  • Ticket numbers are randomly generated (not sequential)
  • +
  • Winner selection uses 8 bytes of cryptographic randomness
  • +
  • No one can predict or influence the outcome
  • +
  • All draws are logged and verifiable
  • +
+
+
+ + {/* Technical Details */} +
+

+ Technical Details +

+
+
+

Payments

+
    +
  • • Lightning Network (LNbits)
  • +
  • • Instant confirmations
  • +
  • • No minimum withdrawal
  • +
+
+
+

Security

+
    +
  • • CSPRNG for all randomness
  • +
  • • Nostr authentication (optional)
  • +
  • • Open source codebase
  • +
+
+
+

Draws

+
    +
  • • Automated on schedule
  • +
  • • Instant winner notification
  • +
  • • Automatic payout retry
  • +
+
+
+

Transparency

+
    +
  • • Public winner history
  • +
  • • Verifiable ticket numbers
  • +
  • • Clear fee structure
  • +
+
+
+
+ + {/* FAQ */} +
+

+ Frequently Asked Questions +

+
+
+

+ Do I need an account? +

+

+ No! You can buy tickets with just a Lightning address. Optionally, you can log in + with Nostr to track your tickets and auto-fill your details. +

+
+
+

+ How do I receive my winnings? +

+

+ Winnings are sent automatically to the Lightning address you provided when buying + tickets. Make sure your address can receive payments! +

+
+
+

+ What happens if payout fails? +

+

+ We automatically retry failed payouts. If it continues to fail after multiple attempts, + a new winner is drawn to ensure the pot is always paid out. +

+
+
+

+ What is the house fee? +

+

+ A small percentage of the pot goes to operating costs. The exact fee is shown before + each draw and the winner receives the pot after fees. +

+
+
+
+ + {/* CTA */} +
+ + Buy Tickets Now + +
+
+
+ ); +} + diff --git a/front_end/src/app/buy/page.tsx b/front_end/src/app/buy/page.tsx new file mode 100644 index 0000000..2465096 --- /dev/null +++ b/front_end/src/app/buy/page.tsx @@ -0,0 +1,367 @@ +'use client'; + +import { useState, useEffect, useRef, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import api from '@/lib/api'; +import { LightningInvoiceCard } from '@/components/LightningInvoiceCard'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import STRINGS from '@/constants/strings'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import { setUser } from '@/store/userSlice'; +import { getAuthToken, hexToNpub, shortNpub } from '@/lib/nostr'; + +export default function BuyPage() { + const router = useRouter(); + const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.user); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [jackpot, setJackpot] = useState(null); + + // Form state + const [lightningAddress, setLightningAddress] = useState(''); + const [lightningAddressTouched, setLightningAddressTouched] = useState(false); + const [buyerName, setBuyerName] = useState('Anon'); + const [buyerNameTouched, setBuyerNameTouched] = useState(false); + const [useNostrName, setUseNostrName] = useState(false); + const [tickets, setTickets] = useState(1); + + // Invoice state + const [invoice, setInvoice] = useState(null); + const [paymentStatus, setPaymentStatus] = useState<'idle' | 'waiting' | 'paid' | 'expired'>('idle'); + const pollIntervalRef = useRef | null>(null); + const expiryTimeoutRef = useRef | null>(null); + const animationTimeoutRef = useRef | null>(null); + const redirectTimeoutRef = useRef | null>(null); + const profilePrefetchAttempted = useRef(false); + const [showPaidAnimation, setShowPaidAnimation] = useState(false); + + const clearPolling = useCallback(() => { + if (pollIntervalRef.current) { + clearInterval(pollIntervalRef.current); + pollIntervalRef.current = null; + } + if (expiryTimeoutRef.current) { + clearTimeout(expiryTimeoutRef.current); + expiryTimeoutRef.current = null; + } + }, []); + + const clearVisualTimers = useCallback(() => { + if (animationTimeoutRef.current) { + clearTimeout(animationTimeoutRef.current); + animationTimeoutRef.current = null; + } + if (redirectTimeoutRef.current) { + clearTimeout(redirectTimeoutRef.current); + redirectTimeoutRef.current = null; + } + }, []); + + useEffect(() => { + loadJackpot(); + return () => { + clearPolling(); + clearVisualTimers(); + }; + }, [clearPolling, clearVisualTimers]); + + useEffect(() => { + if (user.authenticated || profilePrefetchAttempted.current) { + return; + } + + const token = getAuthToken(); + if (!token) { + return; + } + + profilePrefetchAttempted.current = true; + + api + .getProfile() + .then((response) => { + if (response.data?.user) { + const computedDisplayName = + response.data.user.display_name || + (response.data.user.nostr_pubkey + ? shortNpub(hexToNpub(response.data.user.nostr_pubkey)) + : null); + + dispatch( + setUser({ + pubkey: response.data.user.nostr_pubkey, + lightning_address: response.data.user.lightning_address, + displayName: computedDisplayName, + token, + }) + ); + } + }) + .catch(() => { + profilePrefetchAttempted.current = false; + }); + }, [dispatch, user.authenticated]); + + useEffect(() => { + if (user.lightning_address && !lightningAddressTouched) { + setLightningAddress(user.lightning_address); + } + }, [user.lightning_address, lightningAddressTouched]); + + useEffect(() => { + if (useNostrName) { + if (user.displayName) { + setBuyerName(user.displayName); + } else if (user.pubkey) { + setBuyerName(shortNpub(hexToNpub(user.pubkey))); + } else { + setBuyerName('Anon'); + } + } else if (!buyerNameTouched) { + setBuyerName('Anon'); + } + }, [useNostrName, user.displayName, user.pubkey, buyerNameTouched]); + + const loadJackpot = async () => { + try { + const response = await api.getNextJackpot(); + if (response.data) { + setJackpot(response.data); + } + } catch (err: any) { + setError(err.message || 'Failed to load jackpot'); + } + }; + + const handleLightningAddressChange = (value: string) => { + if (!lightningAddressTouched) { + setLightningAddressTouched(true); + } + setLightningAddress(value); + }; + + const handleBuyerNameChange = (value: string) => { + if (!buyerNameTouched) { + setBuyerNameTouched(true); + } + setUseNostrName(false); + setBuyerName(value); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setLoading(true); + + try { + const finalName = buyerName.trim() || 'Anon'; + const response = await api.buyTickets(lightningAddress, tickets, finalName); + + if (response.data) { + setInvoice(response.data); + setPaymentStatus('waiting'); + setShowPaidAnimation(false); + clearVisualTimers(); + startPolling(response.data.ticket_purchase_id); + } + } catch (err: any) { + setError(err.message || 'Failed to create purchase'); + } finally { + setLoading(false); + } + }; + + const startPolling = (purchaseId?: string) => { + if (!purchaseId) { + console.error('Missing purchase ID for polling'); + setError('Missing purchase identifier. Please try again.'); + return; + } + + clearPolling(); + clearVisualTimers(); + setShowPaidAnimation(false); + + pollIntervalRef.current = setInterval(async () => { + try { + const response = await api.getTicketStatus(purchaseId); + + if (response.data.purchase.invoice_status === 'paid') { + setPaymentStatus('paid'); + clearPolling(); + setShowPaidAnimation(true); + + // Redirect after showing the paid animation + redirectTimeoutRef.current = setTimeout(() => { + router.push(`/tickets/${purchaseId}`); + }, 2500); + } + } catch (err) { + console.error('Polling error:', err); + } + }, 5000); // Poll every 5 seconds + + // Stop polling after 20 minutes if still unpaid + expiryTimeoutRef.current = setTimeout(() => { + setPaymentStatus((prev) => { + if (prev === 'waiting') { + clearPolling(); + return 'expired'; + } + return prev; + }); + }, 20 * 60 * 1000); + }; + + if (!jackpot) { + return ; + } + + const ticketPriceSats = jackpot.lottery.ticket_price_sats; + const totalCost = ticketPriceSats * tickets; + + return ( +
+

+ {STRINGS.buy.title} +

+ + {!invoice ? ( + /* Purchase Form */ +
+
+ {/* Lightning Address */} +
+ + handleLightningAddressChange(e.target.value)} + placeholder={STRINGS.buy.lightningAddressPlaceholder} + required + className="w-full bg-gray-800 text-white px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-bitcoin-orange" + /> +

+ Where to send your winnings +

+
+ + {/* Buyer Name */} +
+
+ + {user.authenticated && ( + + )} +
+ handleBuyerNameChange(e.target.value)} + placeholder={STRINGS.buy.buyerNamePlaceholder} + disabled={useNostrName} + maxLength={64} + className={`w-full bg-gray-800 text-white px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-bitcoin-orange ${ + useNostrName ? 'opacity-70 cursor-not-allowed' : '' + }`} + /> +

+ {STRINGS.buy.buyerNameHelp} +

+
+ + {/* Number of Tickets */} +
+ + setTickets(Math.max(1, Math.min(100, parseInt(e.target.value) || 1)))} + min="1" + max="100" + required + className="w-full bg-gray-800 text-white px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-bitcoin-orange" + /> +
+ + {/* Pricing Info */} +
+
+ {STRINGS.buy.ticketPrice} + {ticketPriceSats.toLocaleString()} sats +
+
+ {STRINGS.buy.totalCost} + {totalCost.toLocaleString()} sats +
+
+ + {error && ( +
+ {error} +
+ )} + + {/* Submit Button */} + +
+
+ ) : ( + /* Invoice Display */ +
+ {paymentStatus === 'paid' ? ( +
+ ✓ {STRINGS.buy.paymentReceived} +
+ ) : paymentStatus === 'expired' ? ( +
+ ⚠️ {STRINGS.buy.invoiceExpired} +
+ ) : ( +
+ {STRINGS.buy.waitingForPayment} +
+ )} + + + +
+

{STRINGS.buy.paymentInstructions}

+ + View ticket status page + +
+
+ )} +
+ ); +} + diff --git a/front_end/src/app/dashboard/page.tsx b/front_end/src/app/dashboard/page.tsx new file mode 100644 index 0000000..4c12f6b --- /dev/null +++ b/front_end/src/app/dashboard/page.tsx @@ -0,0 +1,159 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import api from '@/lib/api'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { hexToNpub, shortNpub, removeAuthToken } from '@/lib/nostr'; +import STRINGS from '@/constants/strings'; +import { logout } from '@/store/userSlice'; + +export default function DashboardPage() { + const router = useRouter(); + const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.user); + const [loading, setLoading] = useState(true); + const [profile, setProfile] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!user.authenticated) { + router.push('/'); + return; + } + + loadProfile(); + }, [user.authenticated]); + + const loadProfile = async () => { + try { + setLoading(true); + const response = await api.getProfile(); + if (response.data) { + setProfile(response.data); + } + } catch (err: any) { + setError(err.message || 'Failed to load profile'); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+
+ ); + } + + return ( +
+
+

+ {STRINGS.dashboard.title} +

+ +
+ + {/* Profile Card */} +
+

+ {STRINGS.dashboard.profile} +

+
+
+ Nostr Public Key: +
+ {profile?.user?.nostr_pubkey ? shortNpub(hexToNpub(profile.user.nostr_pubkey)) : 'N/A'} +
+
+ {profile?.user?.lightning_address && ( +
+ Lightning Address: +
{profile.user.lightning_address}
+
+ )} +
+
+ + {/* Stats */} +
+
+
+ {STRINGS.dashboard.currentRoundTickets} +
+
+ {(profile?.stats?.current_round_tickets || 0).toLocaleString()} +
+
+
+
+ {STRINGS.dashboard.pastTickets} +
+
+ {(profile?.stats?.past_tickets || 0).toLocaleString()} +
+
+
+
+ {STRINGS.dashboard.totalWins} +
+
+ {(profile?.stats?.total_wins || 0).toLocaleString()} +
+
+
+
+ {STRINGS.dashboard.totalWinnings} +
+
+ {(profile?.stats?.total_winnings_sats || 0).toLocaleString()} + sats +
+
+
+ + {/* Quick Links */} +
+ +
🎫
+

+ {STRINGS.dashboard.tickets} +

+

View your ticket purchase history

+ + + +
🏆
+

+ {STRINGS.dashboard.wins} +

+

Check your wins and payouts

+ +
+
+ ); +} + diff --git a/front_end/src/app/dashboard/tickets/page.tsx b/front_end/src/app/dashboard/tickets/page.tsx new file mode 100644 index 0000000..1dfdfa3 --- /dev/null +++ b/front_end/src/app/dashboard/tickets/page.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { useAppSelector } from '@/store/hooks'; +import api from '@/lib/api'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { relativeTime } from '@/lib/format'; +import STRINGS from '@/constants/strings'; + +export default function DashboardTicketsPage() { + const router = useRouter(); + const user = useAppSelector((state) => state.user); + const [loading, setLoading] = useState(true); + const [tickets, setTickets] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + if (!user.authenticated) { + router.push('/'); + return; + } + + loadTickets(); + }, [user.authenticated]); + + const loadTickets = async () => { + try { + setLoading(true); + const response = await api.getUserTickets(); + if (response.data) { + setTickets(response.data.purchases || []); + } + } catch (err: any) { + setError(err.message || 'Failed to load tickets'); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+
+ ); + } + + return ( +
+ + + {STRINGS.dashboard.backToDashboard} + +

+ {STRINGS.dashboard.tickets} +

+ + {tickets.length === 0 ? ( +
+
🎫
+
{STRINGS.empty.noTickets}
+ + {STRINGS.empty.buyNow} + +
+ ) : ( +
+ {tickets.map((ticket) => ( + +
+
+
+ {relativeTime(ticket.created_at)} +
+
+ {ticket.id.substring(0, 16)}... +
+
+
+ {ticket.invoice_status} +
+
+
+
+ Tickets: + {ticket.number_of_tickets} +
+
+ Amount: + {ticket.amount_sats.toLocaleString()} sats +
+
+ + ))} +
+ )} +
+ ); +} + diff --git a/front_end/src/app/dashboard/wins/page.tsx b/front_end/src/app/dashboard/wins/page.tsx new file mode 100644 index 0000000..a4bbc60 --- /dev/null +++ b/front_end/src/app/dashboard/wins/page.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { useAppSelector } from '@/store/hooks'; +import api from '@/lib/api'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { formatDateTime } from '@/lib/format'; +import STRINGS from '@/constants/strings'; + +export default function DashboardWinsPage() { + const router = useRouter(); + const user = useAppSelector((state) => state.user); + const [loading, setLoading] = useState(true); + const [wins, setWins] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + if (!user.authenticated) { + router.push('/'); + return; + } + + loadWins(); + }, [user.authenticated]); + + const loadWins = async () => { + try { + setLoading(true); + const response = await api.getUserWins(); + if (response.data) { + setWins(response.data.wins || []); + } + } catch (err: any) { + setError(err.message || 'Failed to load wins'); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+
+ ); + } + + return ( +
+ + + {STRINGS.dashboard.backToDashboard} + +

+ {STRINGS.dashboard.wins} +

+ + {wins.length === 0 ? ( +
+
🏆
+
{STRINGS.empty.noWins}
+
+ ) : ( +
+ {wins.map((win) => ( +
+
+
+
+ 🎉 {win.amount_sats.toLocaleString()} sats +
+
+ {formatDateTime(win.created_at)} +
+
+
+ {win.status} +
+
+
+ Cycle ID: {win.cycle_id.substring(0, 16)}... +
+
+ ))} +
+ )} +
+ ); +} + diff --git a/front_end/src/app/globals.css b/front_end/src/app/globals.css new file mode 100644 index 0000000..3eaf00a --- /dev/null +++ b/front_end/src/app/globals.css @@ -0,0 +1,53 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 218, 218, 218; + --background-rgb: 11, 11, 11; +} + +body { + color: rgb(var(--foreground-rgb)); + background: rgb(var(--background-rgb)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } + + .animate-fade-in { + animation: fade-in 0.5s ease-out forwards; + } +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #0b0b0b; +} + +::-webkit-scrollbar-thumb { + background: #333; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + diff --git a/front_end/src/app/layout.tsx b/front_end/src/app/layout.tsx new file mode 100644 index 0000000..6985394 --- /dev/null +++ b/front_end/src/app/layout.tsx @@ -0,0 +1,36 @@ +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import './globals.css'; +import { Providers } from './providers'; +import { TopBar } from '@/components/TopBar'; +import { Footer } from '@/components/Footer'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'Lightning Lottery - Win Bitcoin', + description: 'Bitcoin Lightning Network powered lottery with instant payouts', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + +
+ +
+ {children} +
+
+
+
+ + + ); +} + diff --git a/front_end/src/app/page.tsx b/front_end/src/app/page.tsx new file mode 100644 index 0000000..333a16b --- /dev/null +++ b/front_end/src/app/page.tsx @@ -0,0 +1,293 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import api from '@/lib/api'; +import { JackpotCountdown } from '@/components/JackpotCountdown'; +import { JackpotPotDisplay } from '@/components/JackpotPotDisplay'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { DrawAnimation } from '@/components/DrawAnimation'; +import STRINGS from '@/constants/strings'; + +interface RecentWinner { + id: string; + winner_name: string; + winner_lightning_address: string; + winning_ticket_serial: number; + pot_after_fee_sats: number; + scheduled_at: string; +} + +export default function HomePage() { + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [jackpot, setJackpot] = useState(null); + const [ticketId, setTicketId] = useState(''); + const [recentWinner, setRecentWinner] = useState(null); + const [showDrawAnimation, setShowDrawAnimation] = useState(false); + const [drawJustCompleted, setDrawJustCompleted] = useState(false); + const [winnerBannerDismissed, setWinnerBannerDismissed] = useState(false); + const [isRecentWin, setIsRecentWin] = useState(false); + + const loadJackpot = useCallback(async () => { + try { + setLoading(true); + const response = await api.getNextJackpot(); + if (response.data) { + setJackpot(response.data); + } + } catch (err: any) { + setError(err.message || 'Failed to load jackpot'); + } finally { + setLoading(false); + } + }, []); + + const loadRecentWinner = useCallback(async () => { + try { + const response = await api.getPastWins(1, 0); + if (response.data?.wins?.length > 0) { + const latestWin = response.data.wins[0]; + const winTime = new Date(latestWin.scheduled_at).getTime(); + const now = Date.now(); + const sixtySeconds = 60 * 1000; + + setRecentWinner(latestWin); + + // Check if this is a recent win (within 60 seconds) + const isRecent = now - winTime < sixtySeconds; + setIsRecentWin(isRecent); + + // If draw completed within last 60 seconds, show animation + if (isRecent && !drawJustCompleted) { + setShowDrawAnimation(true); + setDrawJustCompleted(true); + setWinnerBannerDismissed(false); + } + } + } catch (err) { + console.error('Failed to load recent winner:', err); + } + }, [drawJustCompleted]); + + useEffect(() => { + loadJackpot(); + loadRecentWinner(); + }, [loadJackpot, loadRecentWinner]); + + // Poll for draw completion when countdown reaches zero + useEffect(() => { + if (!jackpot?.cycle?.scheduled_at) return; + + const checkForDraw = () => { + const scheduledTime = new Date(jackpot.cycle.scheduled_at).getTime(); + const now = Date.now(); + + // If we're past the scheduled time, start polling for the winner + if (now >= scheduledTime && !drawJustCompleted) { + loadRecentWinner(); + } + }; + + const interval = setInterval(checkForDraw, 5000); + return () => clearInterval(interval); + }, [jackpot?.cycle?.scheduled_at, drawJustCompleted, loadRecentWinner]); + + const handleCheckTicket = () => { + if (ticketId.trim()) { + router.push(`/tickets/${ticketId.trim()}`); + } + }; + + const handleAnimationComplete = () => { + setShowDrawAnimation(false); + }; + + const handlePlayAgain = () => { + setDrawJustCompleted(false); + setWinnerBannerDismissed(true); + setIsRecentWin(false); + loadJackpot(); + loadRecentWinner(); + }; + + const handleDismissWinnerBanner = () => { + setWinnerBannerDismissed(true); + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+ +
+ ); + } + + if (!jackpot) { + return ( +
+ No active jackpot available +
+ ); + } + + // Only show winner banner if: recent win (within 60s), not dismissed, and animation not showing + const showWinnerBanner = isRecentWin && recentWinner && !showDrawAnimation && !winnerBannerDismissed; + + return ( +
+ {/* Draw Animation Overlay */} + {showDrawAnimation && recentWinner && ( + + )} + + {/* Hero Section */} +
+

+ {STRINGS.app.title} +

+

{STRINGS.app.tagline}

+
+ + {/* Recent Winner Banner - Only shown for 60 seconds after draw */} + {showWinnerBanner && ( +
+ {/* Close button */} + +
+
+ 🏆 Latest Winner +
+
+ {recentWinner.winner_name || 'Anon'} +
+
+ Won {recentWinner.pot_after_fee_sats.toLocaleString()} sats +
+
+ Ticket #{recentWinner.winning_ticket_serial.toLocaleString()} +
+
+
+ )} + + {/* Current Jackpot Card */} +
+

+ {STRINGS.home.currentJackpot} +

+ + {/* Pot Display */} +
+ +
+ + {/* Countdown */} +
+
+ {STRINGS.home.drawIn} +
+
+ +
+
+ + {/* Ticket Price */} +
+ Ticket Price: {jackpot.lottery.ticket_price_sats.toLocaleString()} sats +
+ + {/* Buy Button - Show Refresh only after draw */} +
+ + {STRINGS.home.buyTickets} + + {drawJustCompleted && ( + + )} +
+
+ + {/* Check Ticket Section */} +
+

+ {STRINGS.home.checkTicket} +

+
+ setTicketId(e.target.value)} + placeholder={STRINGS.home.ticketIdPlaceholder} + className="flex-1 bg-gray-800 text-white px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-bitcoin-orange" + onKeyPress={(e) => e.key === 'Enter' && handleCheckTicket()} + /> + +
+
+ + {/* Info Section */} +
+
+
+

Instant

+

+ Lightning-fast ticket purchases and payouts +

+
+
+
🔒
+

Secure

+

+ Cryptographically secure random number generation +

+
+
+
🎯
+

Fair

+

+ Transparent draws with verifiable results +

+
+
+
+ ); +} diff --git a/front_end/src/app/past-wins/page.tsx b/front_end/src/app/past-wins/page.tsx new file mode 100644 index 0000000..c47027d --- /dev/null +++ b/front_end/src/app/past-wins/page.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import api from '@/lib/api'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import STRINGS from '@/constants/strings'; +import { formatDateTime } from '@/lib/format'; + +interface PastWin { + cycle_id: string; + cycle_type: string; + scheduled_at: string; + pot_total_sats: number; + pot_after_fee_sats: number | null; + winner_name: string; + winning_ticket_serial: number | null; +} + +export default function PastWinsPage() { + const [wins, setWins] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadWins = async () => { + try { + const response = await api.getPastWins(); + setWins(response.data?.wins || []); + } catch (err: any) { + setError(err.message || 'Failed to load past wins'); + } finally { + setLoading(false); + } + }; + + loadWins(); + }, []); + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+

Please try again in a moment.

+
+ ); + } + + return ( +
+
+

+ {STRINGS.pastWins.title} +

+

{STRINGS.pastWins.description}

+
+ + {wins.length === 0 ? ( +
+
+
{STRINGS.pastWins.noWins}
+
+ ) : ( +
+ {wins.map((win) => ( +
+
+
+
+ {win.cycle_type} • {formatDateTime(win.scheduled_at)} +
+
+ {win.cycle_id.substring(0, 16)}... +
+
+
+
{STRINGS.pastWins.pot}
+
+ {win.pot_total_sats.toLocaleString()} sats +
+
+
+ +
+
+
{STRINGS.pastWins.winner}
+
+ {win.winner_name || 'Anon'} +
+
+
+
{STRINGS.pastWins.ticket}
+
+ {win.winning_ticket_serial !== null + ? `#${win.winning_ticket_serial}` + : 'N/A'} +
+
+
+
{STRINGS.pastWins.drawTime}
+
{formatDateTime(win.scheduled_at)}
+
+
+
+ ))} +
+ )} +
+ ); +} + diff --git a/front_end/src/app/providers.tsx b/front_end/src/app/providers.tsx new file mode 100644 index 0000000..a68faf5 --- /dev/null +++ b/front_end/src/app/providers.tsx @@ -0,0 +1,9 @@ +'use client'; + +import { Provider } from 'react-redux'; +import { store } from '@/store'; + +export function Providers({ children }: { children: React.ReactNode }) { + return {children}; +} + diff --git a/front_end/src/app/tickets/[id]/page.tsx b/front_end/src/app/tickets/[id]/page.tsx new file mode 100644 index 0000000..20a12a3 --- /dev/null +++ b/front_end/src/app/tickets/[id]/page.tsx @@ -0,0 +1,192 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useParams } from 'next/navigation'; +import api from '@/lib/api'; +import { JackpotCountdown } from '@/components/JackpotCountdown'; +import { TicketList } from '@/components/TicketList'; +import { PayoutStatus } from '@/components/PayoutStatus'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { formatDateTime } from '@/lib/format'; +import STRINGS from '@/constants/strings'; + +export default function TicketStatusPage() { + const params = useParams(); + const ticketId = params.id as string; + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [data, setData] = useState(null); + const [autoRefresh, setAutoRefresh] = useState(true); + + useEffect(() => { + loadTicketStatus(); + + // Auto-refresh if payment pending or draw not complete + const interval = setInterval(() => { + if (autoRefresh) { + loadTicketStatus(true); + } + }, 5000); + + return () => clearInterval(interval); + }, [ticketId, autoRefresh]); + + const loadTicketStatus = async (silent = false) => { + try { + if (!silent) setLoading(true); + + const response = await api.getTicketStatus(ticketId); + + if (response.data) { + setData(response.data); + + // Stop auto-refresh if payment is complete and draw is done + if ( + response.data.purchase.invoice_status === 'paid' && + response.data.cycle.status === 'completed' + ) { + setAutoRefresh(false); + } + } + } catch (err: any) { + setError(err.message || 'Failed to load ticket status'); + } finally { + if (!silent) setLoading(false); + } + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+
⚠️ {error}
+ +
+ ); + } + + if (!data) { + return
Ticket not found
; + } + + const { purchase, tickets, cycle, result } = data; + + return ( +
+

+ {STRINGS.ticket.title} +

+ + {/* Purchase Info */} +
+
+
+ Purchase ID: +
{purchase.id}
+
+
+ Status: +
{purchase.invoice_status}
+
+
+ Tickets: +
{purchase.number_of_tickets}
+
+
+ Amount: +
{purchase.amount_sats.toLocaleString()} sats
+
+
+
+ + {/* Payment Status */} + {purchase.invoice_status === 'pending' && ( +
+ {STRINGS.ticket.waiting} +
+ )} + + {/* Tickets */} + {purchase.ticket_issue_status === 'issued' && ( +
+

+ {STRINGS.ticket.ticketNumbers} +

+ +
+ )} + + {/* Draw Info */} +
+

+ Draw Information +

+ +
+
+ Draw Time: +
{formatDateTime(cycle.scheduled_at)}
+
+ +
+ Current Pot: +
+ {cycle.pot_total_sats.toLocaleString()} sats +
+
+ + {cycle.status !== 'completed' && ( +
+ Time Until Draw: + +
+ )} +
+
+ + {/* Results */} + {result.has_drawn && ( +
+

+ Draw Results +

+ + {result.is_winner ? ( +
+
+ 🎉 {STRINGS.ticket.congratulations} +
+ {result.payout && ( + + )} +
+ ) : ( +
+
+
{STRINGS.ticket.betterLuck}
+ {cycle.winning_ticket_id && ( +
+ {STRINGS.ticket.winningTicket}: #{cycle.winning_ticket_id.substring(0, 8)} +
+ )} +
+
+ )} +
+ )} +
+ ); +} + diff --git a/front_end/src/components/DrawAnimation.tsx b/front_end/src/components/DrawAnimation.tsx new file mode 100644 index 0000000..afdaf18 --- /dev/null +++ b/front_end/src/components/DrawAnimation.tsx @@ -0,0 +1,214 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +interface DrawAnimationProps { + winnerName: string; + winningTicket: number; + potAmount: number; + onComplete: () => void; +} + +export function DrawAnimation({ + winnerName, + winningTicket, + potAmount, + onComplete, +}: DrawAnimationProps) { + const [phase, setPhase] = useState<'spinning' | 'revealing' | 'winner' | 'done'>('spinning'); + const [displayTicket, setDisplayTicket] = useState(0); + const [showConfetti, setShowConfetti] = useState(false); + + // Generate random ticket numbers during spin + useEffect(() => { + if (phase !== 'spinning') return; + + const spinInterval = setInterval(() => { + setDisplayTicket(Math.floor(Math.random() * 999999999) + 1); + }, 50); + + // After 2.5 seconds, start revealing + const revealTimeout = setTimeout(() => { + setPhase('revealing'); + }, 2500); + + return () => { + clearInterval(spinInterval); + clearTimeout(revealTimeout); + }; + }, [phase]); + + // Slow down and reveal actual number + useEffect(() => { + if (phase !== 'revealing') return; + + let speed = 50; + let iterations = 0; + const maxIterations = 15; + + const slowDown = () => { + if (iterations >= maxIterations) { + setDisplayTicket(winningTicket); + setPhase('winner'); + return; + } + + speed += 30; + iterations++; + setDisplayTicket(Math.floor(Math.random() * 999999999) + 1); + + setTimeout(slowDown, speed); + }; + + slowDown(); + }, [phase, winningTicket]); + + // Show winner and confetti + useEffect(() => { + if (phase !== 'winner') return; + + setShowConfetti(true); + + // Auto-dismiss after 6 seconds + const dismissTimeout = setTimeout(() => { + setPhase('done'); + onComplete(); + }, 6000); + + return () => clearTimeout(dismissTimeout); + }, [phase, onComplete]); + + const handleDismiss = useCallback(() => { + setPhase('done'); + onComplete(); + }, [onComplete]); + + if (phase === 'done') return null; + + return ( +
+ {/* Confetti Effect */} + {showConfetti && ( +
+ {[...Array(50)].map((_, i) => ( +
+ ))} +
+ )} + +
+ {/* Spinning Phase */} + {(phase === 'spinning' || phase === 'revealing') && ( + <> +
+ 🎰 Drawing Winner... +
+
+
Ticket Number
+
+ #{displayTicket.toLocaleString()} +
+
+ + )} + + {/* Winner Phase */} + {phase === 'winner' && ( +
+
🎉🏆🎉
+
+ We Have a Winner! +
+
+
Winner
+
+ {winnerName || 'Anon'} +
+
Winning Ticket
+
+ #{winningTicket.toLocaleString()} +
+
Prize
+
+ {potAmount.toLocaleString()} sats +
+
+
+ Click anywhere to continue +
+
+ )} +
+ + +
+ ); +} + diff --git a/front_end/src/components/Footer.tsx b/front_end/src/components/Footer.tsx new file mode 100644 index 0000000..4620465 --- /dev/null +++ b/front_end/src/components/Footer.tsx @@ -0,0 +1,29 @@ +import Link from 'next/link'; + +export function Footer() { + return ( +
+
+
+
+ © 2025 Lightning Lottery. Powered by Bitcoin Lightning Network. +
+
+ + About + + + Past Winners + +
+
+
+
+ ); +} diff --git a/front_end/src/components/JackpotCountdown.tsx b/front_end/src/components/JackpotCountdown.tsx new file mode 100644 index 0000000..cdcbe94 --- /dev/null +++ b/front_end/src/components/JackpotCountdown.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { formatCountdown } from '@/lib/format'; + +interface JackpotCountdownProps { + scheduledAt: string; +} + +export function JackpotCountdown({ scheduledAt }: JackpotCountdownProps) { + const [countdown, setCountdown] = useState(formatCountdown(scheduledAt)); + + useEffect(() => { + const interval = setInterval(() => { + setCountdown(formatCountdown(scheduledAt)); + }, 1000); + + return () => clearInterval(interval); + }, [scheduledAt]); + + if (countdown.total <= 0) { + return
Drawing Now!
; + } + + return ( +
+ {countdown.days > 0 && ( +
+
+ {countdown.days} +
+
days
+
+ )} +
+
+ {countdown.hours.toString().padStart(2, '0')} +
+
hours
+
+
:
+
+
+ {countdown.minutes.toString().padStart(2, '0')} +
+
minutes
+
+
:
+
+
+ {countdown.seconds.toString().padStart(2, '0')} +
+
seconds
+
+
+ ); +} + diff --git a/front_end/src/components/JackpotPotDisplay.tsx b/front_end/src/components/JackpotPotDisplay.tsx new file mode 100644 index 0000000..6976cff --- /dev/null +++ b/front_end/src/components/JackpotPotDisplay.tsx @@ -0,0 +1,20 @@ +import { formatSats, satsToBTC } from '@/lib/format'; + +interface JackpotPotDisplayProps { + potTotalSats: number; +} + +export function JackpotPotDisplay({ potTotalSats }: JackpotPotDisplayProps) { + return ( +
+
+ {formatSats(potTotalSats)} + sats +
+
+ ≈ {satsToBTC(potTotalSats)} BTC +
+
+ ); +} + diff --git a/front_end/src/components/LightningInvoiceCard.tsx b/front_end/src/components/LightningInvoiceCard.tsx new file mode 100644 index 0000000..2bca43b --- /dev/null +++ b/front_end/src/components/LightningInvoiceCard.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { useState } from 'react'; +import { QRCodeSVG } from 'qrcode.react'; + +interface LightningInvoiceCardProps { + paymentRequest: string; + amountSats: number; + showPaidAnimation?: boolean; +} + +export function LightningInvoiceCard({ + paymentRequest, + amountSats, + showPaidAnimation = false, +}: LightningInvoiceCardProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(paymentRequest); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + + return ( +
+ {/* QR Code Container */} +
+
+ +
+ + {/* Paid Overlay - Smooth Green Circle with Checkmark */} +
+
+ + + + +
+
+
+ + {/* Paid Status Text */} +
+
Payment Received!
+
+ + {/* Amount */} +
+
+ {amountSats.toLocaleString()} sats +
+
+ + {/* Invoice */} +
+
+ {paymentRequest} +
+
+ + {/* Copy Button */} + + + +
+ ); +} diff --git a/front_end/src/components/LoadingSpinner.tsx b/front_end/src/components/LoadingSpinner.tsx new file mode 100644 index 0000000..bae4e5e --- /dev/null +++ b/front_end/src/components/LoadingSpinner.tsx @@ -0,0 +1,11 @@ +import STRINGS from '@/constants/strings'; + +export function LoadingSpinner() { + return ( +
+
+
{STRINGS.loading}
+
+ ); +} + diff --git a/front_end/src/components/NostrLoginButton.tsx b/front_end/src/components/NostrLoginButton.tsx new file mode 100644 index 0000000..5904116 --- /dev/null +++ b/front_end/src/components/NostrLoginButton.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { useState } from 'react'; +import { useAppDispatch } from '@/store/hooks'; +import { setUser } from '@/store/userSlice'; +import { getNostrPublicKey, signNostrMessage, storeAuthToken, shortNpub, hexToNpub } from '@/lib/nostr'; +import api from '@/lib/api'; + +export function NostrLoginButton() { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const dispatch = useAppDispatch(); + + const handleLogin = async () => { + setLoading(true); + setError(null); + + try { + // Get Nostr public key + const pubkey = await getNostrPublicKey(); + + // Generate nonce + const nonce = Math.random().toString(36).substring(7); + + // Sign message + const signature = await signNostrMessage(nonce); + + // Authenticate with backend + const response = await api.nostrAuth(pubkey, signature, nonce); + + if (response.data && response.data.token) { + // Store token + storeAuthToken(response.data.token); + + const displayName = + response.data.user.display_name || + shortNpub(hexToNpub(response.data.user.nostr_pubkey)); + + // Update Redux state + dispatch( + setUser({ + pubkey: response.data.user.nostr_pubkey, + lightning_address: response.data.user.lightning_address, + token: response.data.token, + displayName, + }) + ); + } + } catch (err: any) { + console.error('Nostr login error:', err); + setError(err.message || 'Failed to login with Nostr'); + } finally { + setLoading(false); + } + }; + + return ( +
+ + {error && ( +
{error}
+ )} +
+ ); +} + diff --git a/front_end/src/components/PayoutStatus.tsx b/front_end/src/components/PayoutStatus.tsx new file mode 100644 index 0000000..09adc7e --- /dev/null +++ b/front_end/src/components/PayoutStatus.tsx @@ -0,0 +1,55 @@ +import STRINGS from '@/constants/strings'; + +interface PayoutStatusProps { + status: 'pending' | 'paid' | 'failed' | null; + amountSats?: number; +} + +export function PayoutStatus({ status, amountSats }: PayoutStatusProps) { + if (!status) { + return null; + } + + const getStatusDisplay = () => { + switch (status) { + case 'pending': + return { + text: STRINGS.payout.pending, + color: 'text-yellow-500', + icon: '⏳', + }; + case 'paid': + return { + text: STRINGS.payout.paid, + color: 'text-green-500', + icon: '✓', + }; + case 'failed': + return { + text: STRINGS.payout.failed, + color: 'text-red-500', + icon: '⚠️', + }; + } + }; + + const display = getStatusDisplay(); + + return ( +
+
+ {STRINGS.ticket.payoutStatus} +
+
+ {display.icon} + {display.text} +
+ {amountSats && ( +
+ Amount: {amountSats.toLocaleString()} sats +
+ )} +
+ ); +} + diff --git a/front_end/src/components/TicketList.tsx b/front_end/src/components/TicketList.tsx new file mode 100644 index 0000000..8db1028 --- /dev/null +++ b/front_end/src/components/TicketList.tsx @@ -0,0 +1,40 @@ +interface Ticket { + id: string; + serial_number: number; + is_winning_ticket: boolean; +} + +interface TicketListProps { + tickets: Ticket[]; +} + +export function TicketList({ tickets }: TicketListProps) { + if (tickets.length === 0) { + return ( +
+ No tickets issued yet +
+ ); + } + + return ( +
+ {tickets.map((ticket) => ( +
+ #{ticket.serial_number} +
+ ))} +
+ ); +} + diff --git a/front_end/src/components/TopBar.tsx b/front_end/src/components/TopBar.tsx new file mode 100644 index 0000000..b2ebbf2 --- /dev/null +++ b/front_end/src/components/TopBar.tsx @@ -0,0 +1,62 @@ +'use client'; + +import Link from 'next/link'; +import { useState } from 'react'; +import { useAppSelector } from '@/store/hooks'; +import { NostrLoginButton } from './NostrLoginButton'; +import { shortNpub, hexToNpub } from '@/lib/nostr'; +import STRINGS from '@/constants/strings'; + +export function TopBar() { + const user = useAppSelector((state) => state.user); + + return ( + + ); +} + diff --git a/front_end/src/config/index.ts b/front_end/src/config/index.ts new file mode 100644 index 0000000..69e5a86 --- /dev/null +++ b/front_end/src/config/index.ts @@ -0,0 +1,7 @@ +export const config = { + apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000', + appBaseUrl: process.env.NEXT_PUBLIC_APP_BASE_URL || 'http://localhost:3001', +}; + +export default config; + diff --git a/front_end/src/constants/strings.ts b/front_end/src/constants/strings.ts new file mode 100644 index 0000000..e3ad691 --- /dev/null +++ b/front_end/src/constants/strings.ts @@ -0,0 +1,96 @@ +// All user-facing text strings +export const STRINGS = { + app: { + title: 'Lightning Lottery', + tagline: 'Win Bitcoin on the Lightning Network', + }, + home: { + currentJackpot: 'Current Jackpot', + drawIn: 'Draw In', + buyTickets: 'Buy Tickets', + checkTicket: 'Check My Ticket', + ticketIdPlaceholder: 'Enter ticket ID', + }, + buy: { + title: 'Buy Lottery Tickets', + lightningAddress: 'Lightning Address', + lightningAddressPlaceholder: 'you@getalby.com', + buyerName: 'Display Name (optional)', + buyerNamePlaceholder: 'Anon', + buyerNameHelp: 'Shown on the public winners board. Leave blank to appear as Anon.', + useNostrName: 'Use my Nostr name', + numberOfTickets: 'Number of Tickets', + ticketPrice: 'Ticket Price', + totalCost: 'Total Cost', + createInvoice: 'Create Invoice', + paymentInstructions: 'Scan QR code or copy invoice to pay', + copyInvoice: 'Copy Invoice', + waitingForPayment: 'Waiting for payment...', + paymentReceived: 'Payment Received!', + invoiceExpired: 'Invoice Expired', + }, + ticket: { + title: 'Ticket Status', + purchaseId: 'Purchase ID', + status: 'Status', + ticketNumbers: 'Your Ticket Numbers', + drawTime: 'Draw Time', + currentPot: 'Current Pot', + waiting: 'Waiting for payment...', + issued: 'Tickets Issued', + drawPending: 'Draw pending...', + congratulations: 'Congratulations! You Won!', + winningTicket: 'Winning Ticket', + betterLuck: 'Better luck next time!', + payoutStatus: 'Payout Status', + }, + payout: { + pending: 'Payout pending...', + paid: 'Paid! 🎉', + failed: 'Payout failed - contact support', + }, + dashboard: { + title: 'My Dashboard', + profile: 'Profile', + tickets: 'My Tickets', + wins: 'My Wins', + stats: 'Statistics', + totalTickets: 'Total Tickets', + currentRoundTickets: 'Current Round Tickets', + pastTickets: 'Past Tickets (Completed Rounds)', + totalWins: 'Total Wins', + totalWinnings: 'Total Winnings', + backToDashboard: 'Back to Dashboard', + }, + pastWins: { + title: 'Past Winners', + description: 'Recent jackpots and their champions.', + noWins: 'No completed jackpots yet. Check back soon!', + winner: 'Winner', + ticket: 'Ticket #', + pot: 'Pot', + drawTime: 'Draw Time', + }, + admin: { + title: 'Admin Dashboard', + cycles: 'Cycles', + payouts: 'Payouts', + runDraw: 'Run Draw', + retryPayout: 'Retry Payout', + }, + errors: { + generic: 'Something went wrong. Please try again.', + nostrNotFound: 'Nostr extension not found', + invalidAddress: 'Invalid Lightning Address', + networkError: 'Network error. Please check your connection.', + }, + loading: 'Loading...', + empty: { + noTickets: 'You have no tickets yet', + noWins: 'No wins yet', + buyNow: 'Buy Tickets Now', + }, +}; + +export default STRINGS; + diff --git a/front_end/src/lib/api.ts b/front_end/src/lib/api.ts new file mode 100644 index 0000000..555ccb2 --- /dev/null +++ b/front_end/src/lib/api.ts @@ -0,0 +1,224 @@ +import config from '@/config'; + +const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '[::1]']); +const EXPLICIT_BACKEND_PORT = process.env.NEXT_PUBLIC_BACKEND_PORT; + +const buildBaseUrl = (protocol: string, hostname: string, port?: string) => { + if (port && port.length > 0) { + return `${protocol}//${hostname}:${port}`; + } + return `${protocol}//${hostname}`; +}; + +const inferPort = (configuredPort?: string, runtimePort?: string, runtimeProtocol?: string): string | undefined => { + if (EXPLICIT_BACKEND_PORT && EXPLICIT_BACKEND_PORT.length > 0) { + return EXPLICIT_BACKEND_PORT; + } + + if (configuredPort && configuredPort.length > 0) { + return configuredPort; + } + + if (runtimePort && runtimePort.length > 0) { + return runtimePort === '3001' ? '3000' : runtimePort; + } + + if (runtimeProtocol === 'https:') { + return undefined; + } + + return undefined; +}; + +const resolveBrowserBaseUrl = (staticBaseUrl: string) => { + if (typeof window === 'undefined') { + return staticBaseUrl; + } + + const { protocol, hostname, port } = window.location; + + if (!staticBaseUrl || staticBaseUrl.length === 0) { + const inferredPort = inferPort(undefined, port, protocol); + return buildBaseUrl(protocol, hostname, inferredPort); + } + + try { + const parsed = new URL(staticBaseUrl); + const shouldSwapHost = LOOPBACK_HOSTS.has(parsed.hostname) && !LOOPBACK_HOSTS.has(hostname); + + if (shouldSwapHost) { + const inferredPort = inferPort(parsed.port, port, parsed.protocol); + return buildBaseUrl(parsed.protocol, hostname, inferredPort); + } + + return staticBaseUrl; + } catch { + return staticBaseUrl; + } +}; + +interface ApiOptions { + method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'; + headers?: Record; + body?: any; + auth?: boolean; +} + +class ApiClient { + private baseUrl: string; + private cachedBrowserBaseUrl: string | null = null; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + private getBaseUrl(): string { + if (typeof window === 'undefined') { + return this.baseUrl || 'http://localhost:3000'; + } + + if (this.cachedBrowserBaseUrl) { + return this.cachedBrowserBaseUrl; + } + + const resolved = resolveBrowserBaseUrl(this.baseUrl || 'http://localhost:3000'); + this.cachedBrowserBaseUrl = resolved; + return resolved; + } + + private getAuthToken(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem('auth_token'); + } + + async request(path: string, options: ApiOptions = {}): Promise { + const { method = 'GET', headers = {}, body, auth = false } = options; + + const url = `${this.getBaseUrl()}${path}`; + + const requestHeaders: Record = { + 'Content-Type': 'application/json', + ...headers, + }; + + if (auth) { + const token = this.getAuthToken(); + if (token) { + requestHeaders['Authorization'] = `Bearer ${token}`; + } + } + + const requestOptions: RequestInit = { + method, + headers: requestHeaders, + }; + + if (body && method !== 'GET') { + requestOptions.body = JSON.stringify(body); + } + + try { + const response = await fetch(url, requestOptions); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'API request failed'); + } + + return data; + } catch (error: any) { + console.error('API request error:', error); + throw error; + } + } + + // Public endpoints + async getNextJackpot() { + return this.request('/jackpot/next'); + } + + async buyTickets(lightningAddress: string, tickets: number, buyerName?: string) { + return this.request('/jackpot/buy', { + method: 'POST', + body: { lightning_address: lightningAddress, tickets, name: buyerName }, + auth: true, + }); + } + + async getTicketStatus(ticketPurchaseId: string) { + return this.request(`/tickets/${ticketPurchaseId}`); + } + + async getPastWins(limit = 25, offset = 0) { + const params = new URLSearchParams({ + limit: String(limit), + offset: String(offset), + }); + return this.request(`/jackpot/past-wins?${params.toString()}`); + } + + // Auth endpoints + async nostrAuth(nostrPubkey: string, signedMessage: string, nonce: string) { + return this.request('/auth/nostr', { + method: 'POST', + body: { nostr_pubkey: nostrPubkey, signed_message: signedMessage, nonce }, + }); + } + + // User endpoints + async getProfile() { + return this.request('/me', { auth: true }); + } + + async updateLightningAddress(lightningAddress: string) { + return this.request('/me/lightning-address', { + method: 'PATCH', + body: { lightning_address: lightningAddress }, + auth: true, + }); + } + + async getUserTickets(limit = 50, offset = 0) { + return this.request(`/me/tickets?limit=${limit}&offset=${offset}`, { auth: true }); + } + + async getUserWins() { + return this.request('/me/wins', { auth: true }); + } + + // Admin endpoints + async listCycles(status?: string, cycleType?: string) { + const params = new URLSearchParams(); + if (status) params.append('status', status); + if (cycleType) params.append('cycle_type', cycleType); + + return this.request(`/admin/cycles?${params.toString()}`, { + headers: { 'X-Admin-Key': process.env.NEXT_PUBLIC_ADMIN_KEY || '' }, + }); + } + + async runDrawManually(cycleId: string) { + return this.request(`/admin/cycles/${cycleId}/run-draw`, { + method: 'POST', + headers: { 'X-Admin-Key': process.env.NEXT_PUBLIC_ADMIN_KEY || '' }, + }); + } + + async listPayouts(status?: string) { + const params = status ? `?status=${status}` : ''; + return this.request(`/admin/payouts${params}`, { + headers: { 'X-Admin-Key': process.env.NEXT_PUBLIC_ADMIN_KEY || '' }, + }); + } + + async retryPayout(payoutId: string) { + return this.request(`/admin/payouts/${payoutId}/retry`, { + method: 'POST', + headers: { 'X-Admin-Key': process.env.NEXT_PUBLIC_ADMIN_KEY || '' }, + }); + } +} + +export const api = new ApiClient(config.apiBaseUrl); +export default api; + diff --git a/front_end/src/lib/format.ts b/front_end/src/lib/format.ts new file mode 100644 index 0000000..6f85151 --- /dev/null +++ b/front_end/src/lib/format.ts @@ -0,0 +1,79 @@ +/** + * Convert sats to BTC + */ +export function satsToBTC(sats: number): string { + return (sats / 100000000).toFixed(8); +} + +/** + * Format sats with thousands separator + */ +export function formatSats(sats: number): string { + return sats.toLocaleString('en-US'); +} + +/** + * Format relative time + */ +export function relativeTime(date: Date | string): string { + const now = new Date(); + const then = new Date(date); + const diffMs = now.getTime() - then.getTime(); + const diffSeconds = Math.floor(diffMs / 1000); + const diffMinutes = Math.floor(diffSeconds / 60); + const diffHours = Math.floor(diffMinutes / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffSeconds < 60) { + return 'just now'; + } else if (diffMinutes < 60) { + return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''} ago`; + } else if (diffHours < 24) { + return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + } else if (diffDays < 7) { + return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + } else { + return then.toLocaleDateString(); + } +} + +/** + * Format countdown time + */ +export function formatCountdown(targetDate: Date | string): { + days: number; + hours: number; + minutes: number; + seconds: number; + total: number; +} { + const now = new Date(); + const target = new Date(targetDate); + const diffMs = target.getTime() - now.getTime(); + + if (diffMs <= 0) { + return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 }; + } + + const days = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diffMs % (1000 * 60)) / 1000); + + return { days, hours, minutes, seconds, total: diffMs }; +} + +/** + * Format date and time + */ +export function formatDateTime(date: Date | string): string { + const d = new Date(date); + return d.toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} + diff --git a/front_end/src/lib/nostr.ts b/front_end/src/lib/nostr.ts new file mode 100644 index 0000000..bdb0f9e --- /dev/null +++ b/front_end/src/lib/nostr.ts @@ -0,0 +1,100 @@ +// Nostr utilities for NIP-07 authentication + +interface NostrWindow extends Window { + nostr?: { + getPublicKey: () => Promise; + signEvent: (event: any) => Promise; + }; +} + +declare let window: NostrWindow; + +/** + * Check if Nostr extension is available (NIP-07) + */ +export function isNostrAvailable(): boolean { + if (typeof window === 'undefined') return false; + return !!window.nostr; +} + +/** + * Get user's Nostr public key + */ +export async function getNostrPublicKey(): Promise { + if (!isNostrAvailable()) { + throw new Error('Nostr extension not found. Please install a Nostr browser extension.'); + } + + try { + const pubkey = await window.nostr!.getPublicKey(); + return pubkey; + } catch (error: any) { + throw new Error('Failed to get Nostr public key: ' + error.message); + } +} + +/** + * Sign a message with Nostr + */ +export async function signNostrMessage(message: string): Promise { + if (!isNostrAvailable()) { + throw new Error('Nostr extension not found'); + } + + try { + // Create a simple event to sign + const event = { + kind: 22242, // Custom kind for auth + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: message, + }; + + const signedEvent = await window.nostr!.signEvent(event); + return signedEvent.sig; + } catch (error: any) { + throw new Error('Failed to sign message: ' + error.message); + } +} + +/** + * Store auth token + */ +export function storeAuthToken(token: string): void { + if (typeof window === 'undefined') return; + localStorage.setItem('auth_token', token); +} + +/** + * Get auth token + */ +export function getAuthToken(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem('auth_token'); +} + +/** + * Remove auth token + */ +export function removeAuthToken(): void { + if (typeof window === 'undefined') return; + localStorage.removeItem('auth_token'); +} + +/** + * Convert hex pubkey to npub format (simplified) + */ +export function hexToNpub(hex: string): string { + // This is a simplified version + // In production, use a proper bech32 encoding library + return `npub1${hex.substring(0, 58)}`; +} + +/** + * Shorten npub for display + */ +export function shortNpub(npub: string): string { + if (npub.length < 16) return npub; + return `${npub.substring(0, 8)}...${npub.substring(npub.length - 4)}`; +} + diff --git a/front_end/src/store/hooks.ts b/front_end/src/store/hooks.ts new file mode 100644 index 0000000..90d21c2 --- /dev/null +++ b/front_end/src/store/hooks.ts @@ -0,0 +1,6 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './index'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; + diff --git a/front_end/src/store/index.ts b/front_end/src/store/index.ts new file mode 100644 index 0000000..b6bd2ad --- /dev/null +++ b/front_end/src/store/index.ts @@ -0,0 +1,16 @@ +import { configureStore } from '@reduxjs/toolkit'; +import userReducer from './userSlice'; +import jackpotReducer from './jackpotSlice'; +import purchaseReducer from './purchaseSlice'; + +export const store = configureStore({ + reducer: { + user: userReducer, + jackpot: jackpotReducer, + purchase: purchaseReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; + diff --git a/front_end/src/store/jackpotSlice.ts b/front_end/src/store/jackpotSlice.ts new file mode 100644 index 0000000..8be216c --- /dev/null +++ b/front_end/src/store/jackpotSlice.ts @@ -0,0 +1,45 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface Cycle { + id: string; + cycle_type: string; + scheduled_at: string; + pot_total_sats: number; + ticket_price_sats: number; + status: string; +} + +interface JackpotState { + cycle: Cycle | null; + loading: boolean; + error: string | null; +} + +const initialState: JackpotState = { + cycle: null, + loading: false, + error: null, +}; + +const jackpotSlice = createSlice({ + name: 'jackpot', + initialState, + reducers: { + setCycle: (state, action: PayloadAction) => { + state.cycle = action.payload; + state.loading = false; + state.error = null; + }, + setLoading: (state, action: PayloadAction) => { + state.loading = action.payload; + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload; + state.loading = false; + }, + }, +}); + +export const { setCycle, setLoading, setError } = jackpotSlice.actions; +export default jackpotSlice.reducer; + diff --git a/front_end/src/store/purchaseSlice.ts b/front_end/src/store/purchaseSlice.ts new file mode 100644 index 0000000..c7eae08 --- /dev/null +++ b/front_end/src/store/purchaseSlice.ts @@ -0,0 +1,45 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface Ticket { + id: string; + serial_number: number; + is_winning_ticket: boolean; +} + +interface PurchaseState { + ticket_purchase_id: string | null; + invoice_status: string | null; + tickets: Ticket[]; + loading: boolean; + error: string | null; +} + +const initialState: PurchaseState = { + ticket_purchase_id: null, + invoice_status: null, + tickets: [], + loading: false, + error: null, +}; + +const purchaseSlice = createSlice({ + name: 'purchase', + initialState, + reducers: { + setPurchase: (state, action: PayloadAction>) => { + return { ...state, ...action.payload, loading: false, error: null }; + }, + setLoading: (state, action: PayloadAction) => { + state.loading = action.payload; + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload; + state.loading = false; + }, + clearPurchase: () => initialState, + }, +}); + +export const { setPurchase, setLoading, setError, clearPurchase } = purchaseSlice.actions; +export default purchaseSlice.reducer; + diff --git a/front_end/src/store/userSlice.ts b/front_end/src/store/userSlice.ts new file mode 100644 index 0000000..3dab0e9 --- /dev/null +++ b/front_end/src/store/userSlice.ts @@ -0,0 +1,32 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface UserState { + authenticated: boolean; + pubkey: string | null; + lightning_address: string | null; + token: string | null; + displayName: string | null; +} + +const initialState: UserState = { + authenticated: false, + pubkey: null, + lightning_address: null, + token: null, + displayName: null, +}; + +const userSlice = createSlice({ + name: 'user', + initialState, + reducers: { + setUser: (state, action: PayloadAction>) => { + return { ...state, ...action.payload, authenticated: true }; + }, + logout: () => initialState, + }, +}); + +export const { setUser, logout } = userSlice.actions; +export default userSlice.reducer; + diff --git a/front_end/tailwind.config.js b/front_end/tailwind.config.js new file mode 100644 index 0000000..039f98b --- /dev/null +++ b/front_end/tailwind.config.js @@ -0,0 +1,22 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + bitcoin: { + orange: '#f7931a', + }, + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, + }, + }, + plugins: [], +} + diff --git a/front_end/tsconfig.json b/front_end/tsconfig.json new file mode 100644 index 0000000..79cde90 --- /dev/null +++ b/front_end/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} +