From 9ffd26c31801757e4f885f3078662a1800b8da9c Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Thu, 3 Sep 2026 23:41:32 +0330 Subject: [PATCH] Initial commit of the Mado Motor NestJS API. --- .env.example | 39 + .gitignore | 6 + README.md | 72 + docker-compose.yml | 25 + eslint.config.mjs | 35 + nest-cli.json | 8 + package-lock.json | 5613 +++++++++++++++++ package.json | 52 + .../20260903171500_init/migration.sql | 238 + .../migration.sql | 8 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 206 + scripts/create-super-admin.ts | 63 + scripts/import-legacy-export.ts | 396 ++ scripts/migrate-images-to-s3.ts | 320 + scripts/send-sms.ts | 53 + src/app.module.ts | 37 + src/auth/auth.controller.ts | 73 + src/auth/auth.module.ts | 33 + src/auth/auth.service.ts | 425 ++ src/auth/auth.types.ts | 20 + src/auth/decorators/current-user.decorator.ts | 9 + src/auth/decorators/roles.decorator.ts | 5 + src/auth/dto/cell-number.dto.ts | 7 + src/auth/dto/forgot-password-reset.dto.ts | 16 + src/auth/dto/login.dto.ts | 13 + src/auth/dto/otp-verify.dto.ts | 12 + src/auth/dto/refresh-token.dto.ts | 7 + src/auth/dto/register-send-code.dto.ts | 19 + src/auth/dto/register-verify.dto.ts | 12 + src/auth/guards/jwt-auth.guard.ts | 5 + src/auth/guards/roles.guard.ts | 35 + src/auth/strategies/jwt.strategy.ts | 44 + src/blogs/blogs.controller.ts | 49 + src/blogs/blogs.module.ts | 10 + src/blogs/blogs.service.ts | 59 + src/blogs/dto/create-blog.dto.ts | 31 + src/blogs/dto/update-blog.dto.ts | 32 + src/call-requests/call-requests.controller.ts | 43 + src/call-requests/call-requests.module.ts | 12 + src/call-requests/call-requests.service.ts | 57 + .../dto/create-call-request.dto.ts | 15 + src/common/districts.ts | 29 + src/common/slugify.ts | 10 + src/customers/customers.controller.ts | 49 + src/customers/customers.module.ts | 10 + src/customers/customers.service.ts | 154 + src/customers/dto/create-customer.dto.ts | 28 + src/customers/dto/update-customer.dto.ts | 16 + src/main.ts | 28 + src/media/dto/media-upload-query.dto.ts | 9 + src/media/media.controller.ts | 36 + src/media/media.module.ts | 12 + src/media/media.service.ts | 85 + src/orders/dto/update-order-status.dto.ts | 7 + src/orders/orders.controller.ts | 40 + src/orders/orders.module.ts | 10 + src/orders/orders.service.ts | 43 + src/prisma/prisma.module.ts | 9 + src/prisma/prisma.service.ts | 16 + src/products/dto/create-product.dto.ts | 32 + src/products/dto/update-product.dto.ts | 34 + src/products/products.controller.ts | 49 + src/products/products.module.ts | 10 + src/products/products.service.ts | 59 + .../dto/public-representation-request.dto.ts | 43 + src/public/public.controller.ts | 160 + src/public/public.module.ts | 10 + .../dto/create-representation.dto.ts | 23 + .../dto/list-representations-query.dto.ts | 8 + .../dto/update-representation.dto.ts | 26 + .../representations.controller.ts | 61 + src/representations/representations.module.ts | 10 + .../representations.service.ts | 77 + src/reviews/reviews.controller.ts | 40 + src/reviews/reviews.module.ts | 10 + src/reviews/reviews.service.ts | 44 + src/sitemap/sitemap.controller.ts | 14 + src/sitemap/sitemap.module.ts | 11 + src/sitemap/sitemap.service.ts | 98 + src/sms/sms.module.ts | 8 + src/sms/sms.service.ts | 120 + src/sms/sms.types.ts | 9 + src/storage/s3-storage.driver.ts | 85 + src/storage/storage-keys.ts | 31 + src/storage/storage.module.ts | 9 + src/storage/storage.service.ts | 20 + src/storage/storage.types.ts | 11 + tsconfig.build.json | 4 + tsconfig.json | 25 + 90 files changed, 9949 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 eslint.config.mjs create mode 100644 nest-cli.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 prisma/migrations/20260903171500_init/migration.sql create mode 100644 prisma/migrations/20260903173411_representation_request_fields/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 scripts/create-super-admin.ts create mode 100644 scripts/import-legacy-export.ts create mode 100644 scripts/migrate-images-to-s3.ts create mode 100644 scripts/send-sms.ts create mode 100644 src/app.module.ts create mode 100644 src/auth/auth.controller.ts create mode 100644 src/auth/auth.module.ts create mode 100644 src/auth/auth.service.ts create mode 100644 src/auth/auth.types.ts create mode 100644 src/auth/decorators/current-user.decorator.ts create mode 100644 src/auth/decorators/roles.decorator.ts create mode 100644 src/auth/dto/cell-number.dto.ts create mode 100644 src/auth/dto/forgot-password-reset.dto.ts create mode 100644 src/auth/dto/login.dto.ts create mode 100644 src/auth/dto/otp-verify.dto.ts create mode 100644 src/auth/dto/refresh-token.dto.ts create mode 100644 src/auth/dto/register-send-code.dto.ts create mode 100644 src/auth/dto/register-verify.dto.ts create mode 100644 src/auth/guards/jwt-auth.guard.ts create mode 100644 src/auth/guards/roles.guard.ts create mode 100644 src/auth/strategies/jwt.strategy.ts create mode 100644 src/blogs/blogs.controller.ts create mode 100644 src/blogs/blogs.module.ts create mode 100644 src/blogs/blogs.service.ts create mode 100644 src/blogs/dto/create-blog.dto.ts create mode 100644 src/blogs/dto/update-blog.dto.ts create mode 100644 src/call-requests/call-requests.controller.ts create mode 100644 src/call-requests/call-requests.module.ts create mode 100644 src/call-requests/call-requests.service.ts create mode 100644 src/call-requests/dto/create-call-request.dto.ts create mode 100644 src/common/districts.ts create mode 100644 src/common/slugify.ts create mode 100644 src/customers/customers.controller.ts create mode 100644 src/customers/customers.module.ts create mode 100644 src/customers/customers.service.ts create mode 100644 src/customers/dto/create-customer.dto.ts create mode 100644 src/customers/dto/update-customer.dto.ts create mode 100644 src/main.ts create mode 100644 src/media/dto/media-upload-query.dto.ts create mode 100644 src/media/media.controller.ts create mode 100644 src/media/media.module.ts create mode 100644 src/media/media.service.ts create mode 100644 src/orders/dto/update-order-status.dto.ts create mode 100644 src/orders/orders.controller.ts create mode 100644 src/orders/orders.module.ts create mode 100644 src/orders/orders.service.ts create mode 100644 src/prisma/prisma.module.ts create mode 100644 src/prisma/prisma.service.ts create mode 100644 src/products/dto/create-product.dto.ts create mode 100644 src/products/dto/update-product.dto.ts create mode 100644 src/products/products.controller.ts create mode 100644 src/products/products.module.ts create mode 100644 src/products/products.service.ts create mode 100644 src/public/dto/public-representation-request.dto.ts create mode 100644 src/public/public.controller.ts create mode 100644 src/public/public.module.ts create mode 100644 src/representations/dto/create-representation.dto.ts create mode 100644 src/representations/dto/list-representations-query.dto.ts create mode 100644 src/representations/dto/update-representation.dto.ts create mode 100644 src/representations/representations.controller.ts create mode 100644 src/representations/representations.module.ts create mode 100644 src/representations/representations.service.ts create mode 100644 src/reviews/reviews.controller.ts create mode 100644 src/reviews/reviews.module.ts create mode 100644 src/reviews/reviews.service.ts create mode 100644 src/sitemap/sitemap.controller.ts create mode 100644 src/sitemap/sitemap.module.ts create mode 100644 src/sitemap/sitemap.service.ts create mode 100644 src/sms/sms.module.ts create mode 100644 src/sms/sms.service.ts create mode 100644 src/sms/sms.types.ts create mode 100644 src/storage/s3-storage.driver.ts create mode 100644 src/storage/storage-keys.ts create mode 100644 src/storage/storage.module.ts create mode 100644 src/storage/storage.service.ts create mode 100644 src/storage/storage.types.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3495391 --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# PostgreSQL (Docker Compose) +POSTGRES_HOST=localhost +POSTGRES_PORT=5435 +POSTGRES_USER=mado +POSTGRES_PASSWORD=mado_secret +POSTGRES_DB=mado_motor + +DATABASE_URL=postgresql://mado:mado_secret@localhost:5435/mado_motor + +# API +PORT=3001 +PUBLIC_SITE_ORIGIN=https://madomotor.ir +CORS_ORIGIN=http://manage.madomotor.com:5175,http://localhost:5175,http://127.0.0.1:5175,http://madomotor.com:5174,http://localhost:5174 + +# JWT +JWT_ACCESS_SECRET=change-me-mado-access-secret-min-32-chars +JWT_REFRESH_SECRET=change-me-mado-refresh-secret-min-32-chars +JWT_ACCESS_EXPIRES_IN=15m +JWT_REFRESH_EXPIRES_IN=7d + +# Object storage (same Parspack bucket as Balout/Meshkee — prefix mado-motor/) +STORAGE_DISK=s3 +S3_ENDPOINT=https://c804387.parspack.net +S3_BUCKET=c804387 +S3_PUBLIC_URL=https://c804387.parspack.net/c804387 +S3_REGION=us-east-1 +S3_FORCE_PATH_STYLE=true +S3_ACCESS_KEY_ID= +S3_SECRET_ACCESS_KEY= + +MEDIA_MAX_FILE_SIZE_MB=10 + +# Meshkee SMS (server-side only — never expose to frontend) +# Add madomotor.ir to Meshkee SMS_PARTNERS with this API key +MESHKEE_SMS_URL=https://api.meshkee.com/api/v1/public/sms/send +MESHKEE_SMS_API_KEY= +MESHKEE_SMS_DOMAIN=madomotor.ir +# Comma-separated admin mobiles for call/representation alerts +SMS_NOTIFY_MOBILES=09127004945 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5189eca --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.env +*.log +.DS_Store +scripts/data/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f3b747 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Mado Motor Admin API + +NestJS + Prisma + PostgreSQL backend for the admin dashboard. + +## Features + +- **Auth**: phone (`09…`) + password, OTP one-time login, forgot-password OTP, register OTP +- **SMS**: Meshkee partner gateway (same as Balout) +- **Images**: Parspack S3 (same bucket as Balout/Meshkee), folder prefix `mado-motor/` +- **Admin CRUD**: products, orders, customers, blogs, representations, reviews, call-requests, media upload + +## Quick start + +```bash +cd backend +cp .env.example .env # fill S3 + Meshkee SMS + JWT secrets +npm install +npm run db:up +npx prisma migrate dev +npm run create-super-admin -- --phone 09127004945 --password 'your-pass' --first علیرضا --last حسنی +npm run start:dev +``` + +API: `http://localhost:3001/api/v1` +Dashboard expects: `VITE_API_URL=http://api.madomotor.com:3001` (add hosts entry → `127.0.0.1`) + +## Storage layout + +``` +mado-motor/ + products/main/{uuid}.jpg + products/gallery/{uuid}.jpg + blogs/{uuid}.jpg + users/{uuid}.jpg + temp/{uuid}.jpg +``` + +Upload: `POST /api/v1/media/upload?kind=main|gallery|blog|temp` (multipart field `file`, admin JWT) + +## SMS (Meshkee) + +Add partner on Meshkee production: + +``` +SMS_PARTNERS=…,madomotor.ir: +``` + +Then set in `.env`: + +``` +MESHKEE_SMS_URL=https://api.meshkee.com/api/v1/public/sms/send +MESHKEE_SMS_API_KEY= +MESHKEE_SMS_DOMAIN=madomotor.ir +``` + +## Auth routes + +| Method | Path | Notes | +|--------|------|-------| +| POST | `/auth/login` | `{ cellNumber, password }` | +| POST | `/auth/login/send-code` | OTP login | +| POST | `/auth/login/verify` | `{ cellNumber, code }` | +| POST | `/auth/forgot-password/*` | send-code / verify / reset | +| POST | `/auth/register/*` | send-code / verify | +| POST | `/auth/refresh` | `{ refreshToken }` | +| GET | `/auth/me` | Bearer | + +## Test SMS + +```bash +npm run send-sms -- --to 09127004945 --message "test" +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..34a4e73 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + postgres: + image: postgres:16-alpine + container_name: mado-postgres + restart: unless-stopped + ports: + - "127.0.0.1:${POSTGRES_PORT:-5435}:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER:-mado} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mado_secret} + POSTGRES_DB: ${POSTGRES_DB:-mado_motor} + volumes: + - mado_postgres_data:/var/lib/postgresql/data + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-mado} -d ${POSTGRES_DB:-mado_motor}", + ] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + mado_postgres_data: diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..4e9f827 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + "prettier/prettier": ["error", { endOfLine: "auto" }], + }, + }, +); diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..aa65693 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5613 @@ +{ + "name": "madomotor-api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "madomotor-api", + "version": "0.0.1", + "license": "UNLICENSED", + "dependencies": { + "@aws-sdk/client-s3": "^3.1101.0", + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.4", + "@nestjs/core": "^11.0.1", + "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.0.1", + "@prisma/client": "^6.19.3", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@types/bcrypt": "^6.0.0", + "@types/express": "^5.0.0", + "@types/multer": "^2.2.0", + "@types/node": "^24.0.0", + "@types/passport-jwt": "^4.0.1", + "prisma": "^6.19.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3" + } + }, + "node_modules/@angular-devkit/core": { + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.27.tgz", + "integrity": "sha512-3amNzoCVSKd7ah6l6lBQL4onwwJvqvam7FMoQBILrxtW5LB5ezh8gMSPuA4zJjKjoRzf9uoWdlzqv/84I52xZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.27.tgz", + "integrity": "sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli": { + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.27.tgz", + "integrity": "sha512-wHYH6SVXVykhLzovUHtYor3Nl4SpIiITi7r9DQDaKYUD4hpRBx25W6N9eGuakT9Vd5tV/x6wmvQFWQZQwFB7eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", + "@inquirer/prompts": "7.3.2", + "ansi-colors": "4.1.3", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.29.tgz", + "integrity": "sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1125.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1125.0.tgz", + "integrity": "sha512-l0npObtjYgqDfUHiifnWGFuAuMVq5r4AVf4C+jY+thJ/2iAr/eHqAjBR4clbmXxSoJ5w3+ZFGRPyci/CCkUoyg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1000.29", + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-node": "^3.972.82", + "@aws-sdk/middleware-sdk-s3": "^3.972.75", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.977.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", + "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@aws-sdk/xml-builder": "^3.972.40", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.33.3", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", + "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", + "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", + "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-login": "^3.972.77", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", + "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", + "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-ini": "^3.973.15", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", + "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", + "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.76", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", + "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.75", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.75.tgz", + "integrity": "sha512-wMIsNumRVKaNMKhvU/s9VrdEwE8S6gSzXp4RygFG5BEMnGkkXf8cjh8zf7cKJBpUDpqTWqwbz5isEgp9rH6Lng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", + "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", + "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1116.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", + "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", + "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", + "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "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/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "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/gen-mapping/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/@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/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/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/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.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/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli": { + "version": "11.0.24", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.24.tgz", + "integrity": "sha512-aIHxQLSYtXShifA3zwWIeznEsZnNa3Iz2QRykFj+sl9IcbERBHr5nH87FRgywM+He3NxoF5WazHfR8FsmVeWxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", + "@angular-devkit/schematics-cli": "19.2.27", + "@inquirer/prompts": "7.10.1", + "@nestjs/schematics": "^11.0.1", + "ansis": "4.2.0", + "chokidar": "4.0.3", + "cli-table3": "0.6.5", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "9.1.0", + "glob": "13.0.6", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.2.0", + "typescript": "5.9.3", + "webpack": "5.106.2", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 20.11" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/common": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.2.3.tgz", + "integrity": "sha512-obdauJXHfthhepbV+LpGe88OeBlR/Kw9lwjLo0Utzc//agoLXYb9DUGhPQWtm81IpBWMv+19eiwcve9MsBZwXA==", + "license": "MIT", + "dependencies": { + "file-type": "21.3.4", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.4.tgz", + "integrity": "sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==", + "license": "MIT", + "dependencies": { + "dotenv": "17.4.1", + "dotenv-expand": "12.0.3", + "lodash": "4.18.1" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/core": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.2.3.tgz", + "integrity": "sha512-vkA9/Ja0Z3hvqXErSa+HaxrfF+cNXthNFi8VPNEKVli4rMd009yExAl0gLmko/Kf8peDXr72u1RN+j9Da2ukHg==", + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.4.2", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/jwt": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/@nestjs/platform-express": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.2.3.tgz", + "integrity": "sha512-YFQvRXT2de1qNL9LJPUBQ31+RsfI4cJ+sbpU9ENM/hDCgoHSEhm7oxUuGGKmhTZBNZEYm8mDYdfoTFmAH1LIJg==", + "license": "MIT", + "dependencies": { + "cors": "2.8.6", + "express": "5.2.1", + "multer": "2.2.0", + "path-to-regexp": "8.4.2", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" + } + }, + "node_modules/@nestjs/schematics": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.1.0.tgz", + "integrity": "sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.24", + "@angular-devkit/schematics": "19.2.24", + "comment-json": "5.0.0", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "typescript": ">=4.8.2" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.24.tgz", + "integrity": "sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "19.2.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.24.tgz", + "integrity": "sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.24", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@smithy/core": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", + "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz", + "integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.0.tgz", + "integrity": "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", + "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.17.2.tgz", + "integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "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/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "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==", + "license": "MIT" + }, + "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/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/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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/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/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "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/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz", + "integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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/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/comment-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-5.0.0.tgz", + "integrity": "sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "esprima": "^4.0.1" + }, + "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/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/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/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", + "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/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/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/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", + "dev": true, + "license": "ISC" + }, + "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/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/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/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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-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-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-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/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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/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/esrecurse/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/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.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/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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-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-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^4.0.1", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "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/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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==", + "dev": true, + "license": "ISC" + }, + "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-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/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/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/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-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-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-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "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/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.13.12", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.12.tgz", + "integrity": "sha512-uLVeV1c9OTk6qkdqnj+mpMD+ZdnZ0szVyWu58HwMmpwkHA1gCEkyjd3veZQXDnuw9KEwSRjcc9B1pS9XKIN1fA==", + "license": "MIT" + }, + "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/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "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.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.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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "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": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multer/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/multer/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/multer/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/multer/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/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.9.tgz", + "integrity": "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, + "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/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": 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==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.2.tgz", + "integrity": "sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.0", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prisma": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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/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/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "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": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/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/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "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/source-map-support/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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/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/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/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/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.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-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "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/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/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.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "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/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=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/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/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/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8a5b74e --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "madomotor-api", + "version": "0.0.1", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "start": "nest start", + "start:dev": "nest start --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:deploy": "prisma migrate deploy", + "db:up": "docker compose up -d", + "db:down": "docker compose down", + "create-super-admin": "ts-node -r tsconfig-paths/register scripts/create-super-admin.ts", + "send-sms": "ts-node -r tsconfig-paths/register scripts/send-sms.ts", + "import:legacy": "ts-node -r tsconfig-paths/register scripts/import-legacy-export.ts --replace", + "migrate:images": "ts-node -r tsconfig-paths/register scripts/migrate-images-to-s3.ts" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.1101.0", + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.4", + "@nestjs/core": "^11.0.1", + "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.0.1", + "@prisma/client": "^6.19.3", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@types/bcrypt": "^6.0.0", + "@types/express": "^5.0.0", + "@types/multer": "^2.2.0", + "@types/node": "^24.0.0", + "@types/passport-jwt": "^4.0.1", + "prisma": "^6.19.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3" + } +} diff --git a/prisma/migrations/20260903171500_init/migration.sql b/prisma/migrations/20260903171500_init/migration.sql new file mode 100644 index 0000000..59a2901 --- /dev/null +++ b/prisma/migrations/20260903171500_init/migration.sql @@ -0,0 +1,238 @@ +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('User', 'Admin', 'Agent', 'superAdmin'); + +-- CreateEnum +CREATE TYPE "OrderStatus" AS ENUM ('pending', 'confirmed', 'preparing', 'shipped', 'delivered', 'cancelled'); + +-- CreateEnum +CREATE TYPE "RepresentationStatus" AS ENUM ('pending', 'active', 'rejected'); + +-- CreateEnum +CREATE TYPE "ReviewStatus" AS ENUM ('pending', 'approved', 'rejected'); + +-- CreateEnum +CREATE TYPE "SmsOtpPurpose" AS ENUM ('register', 'login', 'resetPassword'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "firstName" TEXT NOT NULL, + "lastName" TEXT NOT NULL, + "cellNumber" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "city" TEXT NOT NULL DEFAULT '', + "role" "UserRole" NOT NULL DEFAULT 'User', + "disabled" BOOLEAN NOT NULL DEFAULT false, + "ordersCount" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RefreshToken" ( + "id" TEXT NOT NULL, + "tokenHash" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SmsOtp" ( + "id" TEXT NOT NULL, + "cellNumber" TEXT NOT NULL, + "purpose" "SmsOtpPurpose" NOT NULL, + "codeHash" TEXT NOT NULL, + "payload" JSONB, + "attempts" INTEGER NOT NULL DEFAULT 0, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "SmsOtp_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Product" ( + "id" TEXT NOT NULL, + "nameFa" TEXT NOT NULL, + "nameEn" TEXT NOT NULL DEFAULT '', + "price" INTEGER NOT NULL DEFAULT 0, + "stock" INTEGER NOT NULL DEFAULT 0, + "category" TEXT NOT NULL DEFAULT '', + "imageUrl" TEXT, + "imageKey" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Product_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Order" ( + "id" SERIAL NOT NULL, + "customerId" TEXT, + "customerName" TEXT NOT NULL, + "customerPhone" TEXT NOT NULL, + "status" "OrderStatus" NOT NULL DEFAULT 'pending', + "itemCount" INTEGER NOT NULL DEFAULT 0, + "note" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Order_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OrderItem" ( + "id" TEXT NOT NULL, + "orderId" INTEGER NOT NULL, + "productId" TEXT, + "nameFa" TEXT NOT NULL, + "quantity" INTEGER NOT NULL DEFAULT 1, + "unitPrice" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BlogPost" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "summary" TEXT NOT NULL DEFAULT '', + "body" TEXT NOT NULL DEFAULT '', + "author" TEXT NOT NULL DEFAULT '', + "imageUrl" TEXT, + "imageKey" TEXT, + "published" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "BlogPost_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Representation" ( + "id" TEXT NOT NULL, + "userId" TEXT, + "name" TEXT NOT NULL, + "owner" TEXT NOT NULL DEFAULT '', + "mobile" TEXT NOT NULL, + "city" TEXT NOT NULL, + "address" TEXT NOT NULL DEFAULT '', + "status" "RepresentationStatus" NOT NULL DEFAULT 'pending', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Representation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Review" ( + "id" TEXT NOT NULL, + "authorId" TEXT, + "author" TEXT NOT NULL, + "product" TEXT NOT NULL, + "rating" INTEGER NOT NULL, + "body" TEXT NOT NULL DEFAULT '', + "status" "ReviewStatus" NOT NULL DEFAULT 'pending', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Review_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CallRequest" ( + "id" TEXT NOT NULL, + "userId" TEXT, + "name" TEXT NOT NULL, + "mobile" TEXT NOT NULL, + "note" TEXT NOT NULL DEFAULT '', + "handled" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CallRequest_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_cellNumber_key" ON "User"("cellNumber"); + +-- CreateIndex +CREATE INDEX "User_role_idx" ON "User"("role"); + +-- CreateIndex +CREATE INDEX "User_disabled_idx" ON "User"("disabled"); + +-- CreateIndex +CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId"); + +-- CreateIndex +CREATE INDEX "RefreshToken_tokenHash_idx" ON "RefreshToken"("tokenHash"); + +-- CreateIndex +CREATE INDEX "SmsOtp_cellNumber_purpose_idx" ON "SmsOtp"("cellNumber", "purpose"); + +-- CreateIndex +CREATE INDEX "SmsOtp_expiresAt_idx" ON "SmsOtp"("expiresAt"); + +-- CreateIndex +CREATE INDEX "Product_category_idx" ON "Product"("category"); + +-- CreateIndex +CREATE INDEX "Product_price_idx" ON "Product"("price"); + +-- CreateIndex +CREATE INDEX "Order_customerId_idx" ON "Order"("customerId"); + +-- CreateIndex +CREATE INDEX "Order_status_idx" ON "Order"("status"); + +-- CreateIndex +CREATE INDEX "Order_createdAt_idx" ON "Order"("createdAt"); + +-- CreateIndex +CREATE INDEX "OrderItem_orderId_idx" ON "OrderItem"("orderId"); + +-- CreateIndex +CREATE INDEX "OrderItem_productId_idx" ON "OrderItem"("productId"); + +-- CreateIndex +CREATE INDEX "Representation_status_idx" ON "Representation"("status"); + +-- CreateIndex +CREATE INDEX "Representation_userId_idx" ON "Representation"("userId"); + +-- CreateIndex +CREATE INDEX "Review_status_idx" ON "Review"("status"); + +-- CreateIndex +CREATE INDEX "CallRequest_handled_idx" ON "CallRequest"("handled"); + +-- CreateIndex +CREATE INDEX "CallRequest_createdAt_idx" ON "CallRequest"("createdAt"); + +-- AddForeignKey +ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Order" ADD CONSTRAINT "Order_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Representation" ADD CONSTRAINT "Representation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Review" ADD CONSTRAINT "Review_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CallRequest" ADD CONSTRAINT "CallRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260903173411_representation_request_fields/migration.sql b/prisma/migrations/20260903173411_representation_request_fields/migration.sql new file mode 100644 index 0000000..ae4868e --- /dev/null +++ b/prisma/migrations/20260903173411_representation_request_fields/migration.sql @@ -0,0 +1,8 @@ +-- AlterTable +ALTER TABLE "Representation" ADD COLUMN "description" TEXT NOT NULL DEFAULT '', +ADD COLUMN "firstName" TEXT NOT NULL DEFAULT '', +ADD COLUMN "landline" TEXT NOT NULL DEFAULT '', +ADD COLUMN "lastName" TEXT NOT NULL DEFAULT '', +ADD COLUMN "nationalCode" TEXT NOT NULL DEFAULT '', +ADD COLUMN "postalCode" TEXT NOT NULL DEFAULT '', +ADD COLUMN "province" TEXT NOT NULL DEFAULT ''; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..de38368 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,206 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum UserRole { + User + Admin + Agent + superAdmin +} + +enum OrderStatus { + pending + confirmed + preparing + shipped + delivered + cancelled +} + +enum RepresentationStatus { + pending + active + rejected +} + +enum ReviewStatus { + pending + approved + rejected +} + +enum SmsOtpPurpose { + register + login + resetPassword +} + +model User { + id String @id @default(cuid()) + firstName String + lastName String + cellNumber String @unique + passwordHash String + city String @default("") + role UserRole @default(User) + disabled Boolean @default(false) + ordersCount Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + refreshTokens RefreshToken[] + orders Order[] + reviews Review[] + callRequests CallRequest[] + representations Representation[] + + @@index([role]) + @@index([disabled]) +} + +model RefreshToken { + id String @id @default(cuid()) + tokenHash String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + expiresAt DateTime + createdAt DateTime @default(now()) + + @@index([userId]) + @@index([tokenHash]) +} + +model SmsOtp { + id String @id @default(cuid()) + cellNumber String + purpose SmsOtpPurpose + codeHash String + payload Json? + attempts Int @default(0) + expiresAt DateTime + createdAt DateTime @default(now()) + + @@index([cellNumber, purpose]) + @@index([expiresAt]) +} + +model Product { + id String @id @default(cuid()) + nameFa String + nameEn String @default("") + price Int @default(0) + stock Int @default(0) + category String @default("") + imageUrl String? + imageKey String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + orderItems OrderItem[] + + @@index([category]) + @@index([price]) +} + +model Order { + id Int @id @default(autoincrement()) + customerId String? + customer User? @relation(fields: [customerId], references: [id], onDelete: SetNull) + customerName String + customerPhone String + status OrderStatus @default(pending) + itemCount Int @default(0) + note String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + items OrderItem[] + + @@index([customerId]) + @@index([status]) + @@index([createdAt]) +} + +model OrderItem { + id String @id @default(cuid()) + orderId Int + order Order @relation(fields: [orderId], references: [id], onDelete: Cascade) + productId String? + product Product? @relation(fields: [productId], references: [id], onDelete: SetNull) + nameFa String + quantity Int @default(1) + unitPrice Int @default(0) + + @@index([orderId]) + @@index([productId]) +} + +model BlogPost { + id String @id @default(cuid()) + title String + summary String @default("") + body String @default("") + author String @default("") + imageUrl String? + imageKey String? + published Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Representation { + id String @id @default(cuid()) + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + name String + owner String @default("") + firstName String @default("") + lastName String @default("") + nationalCode String @default("") + mobile String + province String @default("") + city String + landline String @default("") + address String @default("") + postalCode String @default("") + description String @default("") + status RepresentationStatus @default(pending) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([status]) + @@index([userId]) +} + +model Review { + id String @id @default(cuid()) + authorId String? + authorUser User? @relation(fields: [authorId], references: [id], onDelete: SetNull) + author String + product String + rating Int + body String @default("") + status ReviewStatus @default(pending) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([status]) +} + +model CallRequest { + id String @id @default(cuid()) + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + name String + mobile String + note String @default("") + handled Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([handled]) + @@index([createdAt]) +} diff --git a/scripts/create-super-admin.ts b/scripts/create-super-admin.ts new file mode 100644 index 0000000..3077c35 --- /dev/null +++ b/scripts/create-super-admin.ts @@ -0,0 +1,63 @@ +/** + * Create the first superAdmin. + * + * Usage: + * npx ts-node -r tsconfig-paths/register scripts/create-super-admin.ts \ + * --phone 09127004945 --password secret123 --first علیرضا --last حسنی + */ +import { PrismaClient, UserRole } from '@prisma/client' +import * as bcrypt from 'bcrypt' + +const prisma = new PrismaClient() + +function arg(name: string, fallback?: string): string { + const idx = process.argv.indexOf(`--${name}`) + if (idx >= 0 && process.argv[idx + 1]) return process.argv[idx + 1] + if (fallback !== undefined) return fallback + throw new Error(`Missing --${name}`) +} + +async function main() { + const cellNumber = arg('phone') + const password = arg('password') + const firstName = arg('first', 'علیرضا') + const lastName = arg('last', 'حسنی') + + if (!/^09\d{9}$/.test(cellNumber)) { + throw new Error('phone must match 09xxxxxxxxx') + } + if (password.length < 4) { + throw new Error('password min length 4') + } + + const existing = await prisma.user.findUnique({ where: { cellNumber } }) + if (existing) { + throw new Error(`User already exists: ${cellNumber}`) + } + + const passwordHash = await bcrypt.hash(password, 10) + const user = await prisma.user.create({ + data: { + firstName, + lastName, + cellNumber, + passwordHash, + role: UserRole.superAdmin, + }, + }) + + console.log('Created superAdmin:', { + id: user.id, + cellNumber: user.cellNumber, + name: `${user.firstName} ${user.lastName}`, + }) +} + +main() + .catch((err) => { + console.error(err.message || err) + process.exit(1) + }) + .finally(async () => { + await prisma.$disconnect() + }) diff --git a/scripts/import-legacy-export.ts b/scripts/import-legacy-export.ts new file mode 100644 index 0000000..339e693 --- /dev/null +++ b/scripts/import-legacy-export.ts @@ -0,0 +1,396 @@ +/** + * Import JSON exported from production MySQL (VM) into local Postgres. + * + * Usage: + * npx ts-node -r tsconfig-paths/register scripts/import-legacy-export.ts + * + * Optional: + * --dir scripts/data + * --replace wipe products/blogs/representations (and non-kept users) before import + */ +import { promises as fs } from 'fs' +import * as path from 'path' +import { + PrismaClient, + RepresentationStatus, + UserRole, +} from '@prisma/client' + +const prisma = new PrismaClient() + +function arg(name: string, fallback?: string): string | undefined { + const idx = process.argv.indexOf(`--${name}`) + if (idx >= 0 && process.argv[idx + 1] && !process.argv[idx + 1].startsWith('--')) { + return process.argv[idx + 1] + } + if (process.argv.includes(`--${name}`)) return 'true' + return fallback +} + +function legacyId(prefix: string, id: string | number): string { + return `legacy-${prefix}-${id}` +} + +function normalizePhone(raw: string | null | undefined): string { + const digits = String(raw ?? '').replace(/\D/g, '') + if (digits.length === 10 && digits.startsWith('9')) return `0${digits}` + if (digits.length === 11 && digits.startsWith('09')) return digits + return digits || '09000000000' +} + +function mapRole(role: string | null | undefined, cellNumber: string): UserRole { + if (cellNumber === '09127004945') return UserRole.superAdmin + switch (role) { + case 'Admin': + return UserRole.Admin + case 'Agent': + return UserRole.Agent + default: + return UserRole.User + } +} + +function mapRequestStatus(status: number): RepresentationStatus { + if (status === 2) return RepresentationStatus.active + if (status === 1) return RepresentationStatus.rejected + return RepresentationStatus.pending +} + +function toDate(value: string | null | undefined): Date | undefined { + if (!value) return undefined + const d = new Date(value) + return Number.isNaN(d.getTime()) ? undefined : d +} + +function phpBcryptToNode(hash: string): string { + // Node bcrypt accepts $2y$ in most versions; normalize to $2b$ just in case. + if (hash.startsWith('$2y$')) return `$2b$${hash.slice(4)}` + return hash +} + +async function readJson(file: string): Promise { + const raw = await fs.readFile(file, 'utf8') + return JSON.parse(raw) as T +} + +async function main() { + const dir = path.resolve( + process.cwd(), + arg('dir', 'scripts/data') ?? 'scripts/data', + ) + const replace = Boolean(arg('replace')) + + type UserRow = { + id: string + first_name: string + last_name: string + mobile: string + password: string + role: string + created_at?: string + updated_at?: string + } + type ProductRow = { + id: string + title_fa: string + title_en: string + price: string + quantity: string + category: string + description: string + image_url: string | null + created_at?: string + updated_at?: string + } + type BlogRow = { + id: string + title: string + summary: string + body: string + author: string + published: boolean + image_url: string | null + created_at?: string + updated_at?: string + } + type AgentRow = { + id: string + name: string + owner: string + national_code: string + mobile: string + phone: string + postal_code: string + address: string + description: string + city: string + province: string + created_at?: string + updated_at?: string + } + type RequestRow = { + id: string + first_name: string + last_name: string + national_code: string + mobile: string + phone: string + address: string + postal_code: string + description: string + status: number + city: string + province: string + created_at?: string + updated_at?: string + } + + const users = await readJson(path.join(dir, 'users.json')) + const products = await readJson(path.join(dir, 'products.json')) + const blogs = await readJson(path.join(dir, 'blogs.json')) + const agents = await readJson(path.join(dir, 'agents.json')) + const requests = await readJson( + path.join(dir, 'agent_requests.json'), + ) + + if (replace) { + console.log('Clearing existing catalog / representation / non-superAdmin users…') + await prisma.orderItem.deleteMany({}) + await prisma.order.deleteMany({}) + await prisma.review.deleteMany({}) + await prisma.callRequest.deleteMany({}) + await prisma.representation.deleteMany({}) + await prisma.blogPost.deleteMany({}) + await prisma.product.deleteMany({}) + await prisma.refreshToken.deleteMany({}) + await prisma.smsOtp.deleteMany({}) + await prisma.user.deleteMany({ + where: { role: { not: UserRole.superAdmin } }, + }) + } + + let usersUpserted = 0 + for (const u of users) { + const cellNumber = normalizePhone(u.mobile) + const id = legacyId('user', u.id) + const role = mapRole(u.role, cellNumber) + const passwordHash = phpBcryptToNode(u.password) + const data = { + firstName: (u.first_name || '—').slice(0, 190), + lastName: (u.last_name || '—').slice(0, 190), + cellNumber, + passwordHash, + role, + disabled: false, + createdAt: toDate(u.created_at), + updatedAt: toDate(u.updated_at), + } + + const byPhone = await prisma.user.findUnique({ where: { cellNumber } }) + if (byPhone) { + await prisma.user.update({ + where: { id: byPhone.id }, + data: { + firstName: data.firstName, + lastName: data.lastName, + passwordHash: data.passwordHash, + // keep local superAdmin elevation + role: + byPhone.role === UserRole.superAdmin || role === UserRole.superAdmin + ? UserRole.superAdmin + : role, + }, + }) + } else { + await prisma.user.upsert({ + where: { id }, + create: { id, ...data }, + update: { + firstName: data.firstName, + lastName: data.lastName, + cellNumber: data.cellNumber, + passwordHash: data.passwordHash, + role: data.role, + }, + }) + } + usersUpserted += 1 + } + + let productsUpserted = 0 + for (const p of products) { + const id = legacyId('product', p.id) + const price = Number.parseInt(String(p.price || '0'), 10) || 0 + const stock = Number.parseInt(String(p.quantity || '0'), 10) || 0 + await prisma.product.upsert({ + where: { id }, + create: { + id, + nameFa: p.title_fa || 'بدون عنوان', + nameEn: p.title_en || '', + price, + stock, + category: p.category || '', + imageUrl: p.image_url || null, + createdAt: toDate(p.created_at), + updatedAt: toDate(p.updated_at), + }, + update: { + nameFa: p.title_fa || 'بدون عنوان', + nameEn: p.title_en || '', + price, + stock, + category: p.category || '', + imageUrl: p.image_url || null, + }, + }) + productsUpserted += 1 + } + + let blogsUpserted = 0 + for (const b of blogs) { + const id = legacyId('blog', b.id) + await prisma.blogPost.upsert({ + where: { id }, + create: { + id, + title: b.title || 'بدون عنوان', + summary: b.summary || '', + body: b.body || '', + author: (b.author || '').trim(), + imageUrl: b.image_url || null, + published: Boolean(b.published), + createdAt: toDate(b.created_at), + updatedAt: toDate(b.updated_at), + }, + update: { + title: b.title || 'بدون عنوان', + summary: b.summary || '', + body: b.body || '', + author: (b.author || '').trim(), + imageUrl: b.image_url || null, + published: Boolean(b.published), + }, + }) + blogsUpserted += 1 + } + + let repsUpserted = 0 + for (const a of agents) { + const id = legacyId('agent', a.id) + const mobile = normalizePhone(a.mobile) + const owner = a.owner || a.name || '' + const nameParts = owner.trim().split(/\s+/) + await prisma.representation.upsert({ + where: { id }, + create: { + id, + name: a.name || owner || `نمایندگی ${a.id}`, + owner, + firstName: nameParts[0] || '', + lastName: nameParts.slice(1).join(' '), + nationalCode: a.national_code || '', + mobile, + province: a.province || '', + city: a.city || '', + landline: a.phone || '', + address: a.address || '', + postalCode: a.postal_code || '', + description: a.description || '', + status: RepresentationStatus.active, + createdAt: toDate(a.created_at), + updatedAt: toDate(a.updated_at), + }, + update: { + name: a.name || owner || `نمایندگی ${a.id}`, + owner, + nationalCode: a.national_code || '', + mobile, + province: a.province || '', + city: a.city || '', + landline: a.phone || '', + address: a.address || '', + postalCode: a.postal_code || '', + description: a.description || '', + status: RepresentationStatus.active, + }, + }) + repsUpserted += 1 + } + + let requestsUpserted = 0 + for (const r of requests) { + const id = legacyId('agent-request', r.id) + const mobile = normalizePhone(r.mobile) + const status = mapRequestStatus(Number(r.status) || 0) + const name = `${r.first_name || ''} ${r.last_name || ''}`.trim() + await prisma.representation.upsert({ + where: { id }, + create: { + id, + name: name || `درخواست ${r.id}`, + owner: name, + firstName: r.first_name || '', + lastName: r.last_name || '', + nationalCode: r.national_code || '', + mobile, + province: r.province || '', + city: r.city || '', + landline: r.phone || '', + address: r.address || '', + postalCode: r.postal_code || '', + description: r.description || '', + status, + createdAt: toDate(r.created_at), + updatedAt: toDate(r.updated_at), + }, + update: { + name: name || `درخواست ${r.id}`, + owner: name, + firstName: r.first_name || '', + lastName: r.last_name || '', + nationalCode: r.national_code || '', + mobile, + province: r.province || '', + city: r.city || '', + landline: r.phone || '', + address: r.address || '', + postalCode: r.postal_code || '', + description: r.description || '', + status, + }, + }) + requestsUpserted += 1 + } + + const counts = { + users: await prisma.user.count(), + products: await prisma.product.count(), + blogs: await prisma.blogPost.count(), + representations: await prisma.representation.count(), + activeReps: await prisma.representation.count({ + where: { status: RepresentationStatus.active }, + }), + pendingReps: await prisma.representation.count({ + where: { status: RepresentationStatus.pending }, + }), + } + + console.log('Imported:', { + usersUpserted, + productsUpserted, + blogsUpserted, + agentsAsActive: repsUpserted, + agentRequests: requestsUpserted, + }) + console.log('DB totals:', counts) +} + +main() + .catch((err) => { + console.error(err) + process.exitCode = 1 + }) + .finally(async () => { + await prisma.$disconnect() + }) diff --git a/scripts/migrate-images-to-s3.ts b/scripts/migrate-images-to-s3.ts new file mode 100644 index 0000000..f66a6fe --- /dev/null +++ b/scripts/migrate-images-to-s3.ts @@ -0,0 +1,320 @@ +/** + * Download images from legacy Parmin storage URLs and re-upload to our Parspack S3. + * Updates Product/BlogPost imageUrl + imageKey, and rewrites URLs inside blog bodies. + * + * Usage: + * npx ts-node -r tsconfig-paths/register scripts/migrate-images-to-s3.ts + */ +import { createHash, randomUUID } from 'crypto' +import { extname } from 'path' +import { PrismaClient } from '@prisma/client' +import { config as loadEnv } from 'dotenv' +import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { + blogMediaKey, + productMainKey, +} from '../src/storage/storage-keys' + +loadEnv() + +const prisma = new PrismaClient() + +const LEGACY_HOST_RE = + /madomotor\.sas\.amin\.parminstorage\.ir|sas\.amin\.parminstorage\.ir/i + +const EXT_MIME: Record = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.webp': 'image/webp', + '.gif': 'image/gif', +} + +function requireEnv(name: string): string { + const v = process.env[name] + if (!v) throw new Error(`Missing env ${name}`) + return v +} + +function publicUrlBase(): string { + return requireEnv('S3_PUBLIC_URL').replace(/\/$/, '') +} + +function isAlreadyOurs(url: string | null | undefined): boolean { + if (!url) return false + return url.startsWith(publicUrlBase()) +} + +function isLegacyUrl(url: string): boolean { + try { + const u = new URL(url) + return LEGACY_HOST_RE.test(u.hostname) + } catch { + return false + } +} + +function guessExt(url: string, contentType: string | null): string { + const pathPart = decodeURIComponent(new URL(url).pathname) + const fromUrl = extname(pathPart).toLowerCase() + if (fromUrl && fromUrl.length <= 5) return fromUrl + if (contentType?.includes('png')) return '.png' + if (contentType?.includes('webp')) return '.webp' + if (contentType?.includes('gif')) return '.gif' + return '.jpg' +} + +function mimeFor(ext: string, contentType: string | null): string { + if (contentType && contentType.startsWith('image/')) { + return contentType.split(';')[0].trim() + } + return EXT_MIME[ext] ?? 'image/jpeg' +} + +function extractUrls(text: string): string[] { + const found = new Set() + const patterns = [ + /https?:\/\/[^\s"'<>]+/g, + /src=["'](https?:\/\/[^"']+)["']/gi, + ] + for (const re of patterns) { + for (const match of text.matchAll(re)) { + const url = (match[1] ?? match[0]).replace(/&/g, '&') + found.add(url) + } + } + return [...found] +} + +type Kind = 'product' | 'blog' | 'blog-body' + +function encodeFetchUrl(url: string): string { + try { + const u = new URL(url) + // Encode each path segment so Persian filenames download reliably + u.pathname = u.pathname + .split('/') + .map((seg) => { + if (!seg) return seg + try { + return encodeURIComponent(decodeURIComponent(seg)) + } catch { + return encodeURIComponent(seg) + } + }) + .join('/') + return u.toString() + } catch { + return encodeURI(url) + } +} + +async function download(url: string): Promise<{ + buffer: Buffer + contentType: string | null +}> { + const candidates = [url, encodeFetchUrl(url)] + let lastError: Error | null = null + + for (const candidate of candidates) { + try { + const res = await fetch(candidate, { + redirect: 'follow', + headers: { + 'User-Agent': 'mado-motor-image-migrator/1.0', + Accept: 'image/*,*/*', + }, + }) + if (!res.ok) { + lastError = new Error(`HTTP ${res.status} for ${candidate}`) + continue + } + const contentType = res.headers.get('content-type') + const buffer = Buffer.from(await res.arrayBuffer()) + if (buffer.length < 32) { + lastError = new Error(`Tiny response (${buffer.length}b) for ${candidate}`) + continue + } + return { buffer, contentType } + } catch (err) { + lastError = err instanceof Error ? err : new Error(String(err)) + } + } + + throw lastError ?? new Error(`Failed to download ${url}`) +} + +async function main() { + const endpoint = requireEnv('S3_ENDPOINT') + const bucket = requireEnv('S3_BUCKET') + const base = publicUrlBase() + + const client = new S3Client({ + endpoint, + region: process.env.S3_REGION || 'us-east-1', + forcePathStyle: (process.env.S3_FORCE_PATH_STYLE || 'true') === 'true', + credentials: { + accessKeyId: requireEnv('S3_ACCESS_KEY_ID'), + secretAccessKey: requireEnv('S3_SECRET_ACCESS_KEY'), + }, + }) + + const products = await prisma.product.findMany({ + select: { id: true, imageUrl: true, imageKey: true }, + }) + const blogs = await prisma.blogPost.findMany({ + select: { id: true, imageUrl: true, imageKey: true, body: true }, + }) + + type Job = { url: string; kind: Kind; entityId?: string } + const jobs: Job[] = [] + const seen = new Set() + + const enqueue = (url: string, kind: Kind, entityId?: string) => { + const clean = url.replace(/[),.;]+$/, '') + if (!isLegacyUrl(clean) || isAlreadyOurs(clean)) return + if (seen.has(clean)) return + seen.add(clean) + jobs.push({ url: clean, kind, entityId }) + } + + for (const p of products) { + if (p.imageUrl) enqueue(p.imageUrl, 'product', p.id) + } + for (const b of blogs) { + if (b.imageUrl) enqueue(b.imageUrl, 'blog', b.id) + for (const u of extractUrls(b.body || '')) { + enqueue(u, 'blog-body') + } + } + + console.log(`Found ${jobs.length} unique legacy URLs to migrate`) + + const urlMap = new Map() + let ok = 0 + let failed = 0 + + for (const [i, job] of jobs.entries()) { + const label = `[${i + 1}/${jobs.length}]` + try { + const { buffer, contentType } = await download(job.url) + const ext = guessExt(job.url, contentType) + const fileName = `${randomUUID()}${ext}` + const key = + job.kind === 'product' + ? productMainKey(fileName) + : blogMediaKey( + job.kind === 'blog-body' ? `body/${fileName}` : fileName, + ) + const contentTypeFinal = mimeFor(ext, contentType) + + await client.send( + new PutObjectCommand({ + Bucket: bucket, + Key: key, + Body: buffer, + ContentType: contentTypeFinal, + }), + ) + + const publicUrl = `${base}/${key}` + urlMap.set(job.url, { url: publicUrl, key }) + + // also map common encoded/decoded variants + try { + urlMap.set(decodeURI(job.url), { url: publicUrl, key }) + } catch { + /* ignore */ + } + urlMap.set(encodeURI(decodeURIComponent(job.url)), { + url: publicUrl, + key, + }) + + ok += 1 + const hash = createHash('sha1').update(buffer).digest('hex').slice(0, 8) + console.log( + `${label} OK ${job.kind} ${buffer.length}b ${hash} → ${key}`, + ) + } catch (err) { + failed += 1 + console.error( + `${label} FAIL ${job.url}`, + err instanceof Error ? err.message : err, + ) + } + } + + // Update products + let productsUpdated = 0 + for (const p of products) { + if (!p.imageUrl) continue + const mapped = urlMap.get(p.imageUrl) || urlMap.get(p.imageUrl.replace(/[),.;]+$/, '')) + if (!mapped) continue + await prisma.product.update({ + where: { id: p.id }, + data: { imageUrl: mapped.url, imageKey: mapped.key }, + }) + productsUpdated += 1 + } + + // Update blogs (cover + body rewrite) + let blogsUpdated = 0 + for (const b of blogs) { + let body = b.body || '' + let changed = false + + for (const [oldUrl, mapped] of urlMap) { + if (body.includes(oldUrl)) { + body = body.split(oldUrl).join(mapped.url) + changed = true + } + } + + // also rewrite any remaining legacy host URLs that we mapped by pathname + const stillLegacy = extractUrls(body).filter(isLegacyUrl) + for (const u of stillLegacy) { + const clean = u.replace(/[),.;]+$/, '') + const mapped = urlMap.get(clean) + if (mapped && body.includes(u)) { + body = body.split(u).join(mapped.url) + changed = true + } + } + + const coverMapped = b.imageUrl + ? urlMap.get(b.imageUrl) || + urlMap.get(b.imageUrl.replace(/[),.;]+$/, '')) + : undefined + + if (!changed && !coverMapped) continue + + await prisma.blogPost.update({ + where: { id: b.id }, + data: { + ...(coverMapped + ? { imageUrl: coverMapped.url, imageKey: coverMapped.key } + : {}), + ...(changed ? { body } : {}), + }, + }) + blogsUpdated += 1 + } + + console.log('Done:', { + migrated: ok, + failed, + productsUpdated, + blogsUpdated, + publicBase: base, + }) +} + +main() + .catch((err) => { + console.error(err) + process.exitCode = 1 + }) + .finally(async () => { + await prisma.$disconnect() + }) diff --git a/scripts/send-sms.ts b/scripts/send-sms.ts new file mode 100644 index 0000000..d72cedc --- /dev/null +++ b/scripts/send-sms.ts @@ -0,0 +1,53 @@ +/** + * Send an SMS via Meshkee (backend-only; never call from frontend). + * + * Usage: + * npm run send-sms -- --to 09127004945 --message "متن پیام" + */ +import { config as loadEnv } from 'dotenv'; +import { resolve } from 'path'; + +loadEnv({ path: resolve(__dirname, '../.env') }); + +function arg(name: string, fallback?: string): string { + const idx = process.argv.indexOf(`--${name}`); + if (idx >= 0 && process.argv[idx + 1]) return process.argv[idx + 1]; + if (fallback !== undefined) return fallback; + throw new Error(`Missing --${name}`); +} + +async function main() { + const to = arg('to'); + const message = arg('message'); + const apiUrl = + process.env.MESHKEE_SMS_URL ?? + 'https://api.meshkee.com/api/v1/public/sms/send'; + const apiKey = process.env.MESHKEE_SMS_API_KEY; + const domain = process.env.MESHKEE_SMS_DOMAIN ?? 'madomotor.ir'; + + if (!apiKey) throw new Error('MESHKEE_SMS_API_KEY is not set in .env'); + if (!/^09\d{9}$/.test(to)) throw new Error('to must match 09xxxxxxxxx'); + if (!message.trim()) throw new Error('message is empty'); + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': apiKey, + }, + body: JSON.stringify({ domain, to, message }), + }); + + const body = await response.json().catch(() => null); + if (!response.ok || !body?.success) { + console.error('SMS send failed', { status: response.status, body }); + process.exit(1); + } + + console.log('SMS sent', { to, serverId: body.serverId }); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..440a943 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { AuthModule } from './auth/auth.module'; +import { BlogsModule } from './blogs/blogs.module'; +import { CallRequestsModule } from './call-requests/call-requests.module'; +import { CustomersModule } from './customers/customers.module'; +import { MediaModule } from './media/media.module'; +import { OrdersModule } from './orders/orders.module'; +import { PrismaModule } from './prisma/prisma.module'; +import { ProductsModule } from './products/products.module'; +import { PublicModule } from './public/public.module'; +import { RepresentationsModule } from './representations/representations.module'; +import { ReviewsModule } from './reviews/reviews.module'; +import { SitemapModule } from './sitemap/sitemap.module'; +import { SmsModule } from './sms/sms.module'; +import { StorageModule } from './storage/storage.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + PrismaModule, + StorageModule, + SmsModule, + AuthModule, + MediaModule, + PublicModule, + SitemapModule, + ProductsModule, + OrdersModule, + CustomersModule, + BlogsModule, + RepresentationsModule, + ReviewsModule, + CallRequestsModule, + ], +}) +export class AppModule {} diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..11406dc --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,73 @@ +import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import type { AuthUser } from './auth.types'; +import { AuthService } from './auth.service'; +import { CurrentUser } from './decorators/current-user.decorator'; +import { CellNumberDto } from './dto/cell-number.dto'; +import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto'; +import { LoginDto } from './dto/login.dto'; +import { OtpVerifyDto } from './dto/otp-verify.dto'; +import { RefreshTokenDto } from './dto/refresh-token.dto'; +import { RegisterSendCodeDto } from './dto/register-send-code.dto'; +import { RegisterVerifyDto } from './dto/register-verify.dto'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; + +@Controller('auth') +export class AuthController { + constructor(private readonly auth: AuthService) {} + + @Post('login') + login(@Body() dto: LoginDto) { + return this.auth.login(dto); + } + + @Post('login/send-code') + loginSendCode(@Body() dto: CellNumberDto) { + return this.auth.loginSendCode(dto); + } + + @Post('login/verify') + loginVerify(@Body() dto: OtpVerifyDto) { + return this.auth.loginVerify(dto); + } + + @Post('forgot-password/send-code') + forgotPasswordSendCode(@Body() dto: CellNumberDto) { + return this.auth.forgotPasswordSendCode(dto); + } + + @Post('forgot-password/verify') + forgotPasswordVerify(@Body() dto: OtpVerifyDto) { + return this.auth.forgotPasswordVerify(dto); + } + + @Post('forgot-password/reset') + forgotPasswordReset(@Body() dto: ForgotPasswordResetDto) { + return this.auth.forgotPasswordReset(dto); + } + + @Post('register/send-code') + registerSendCode(@Body() dto: RegisterSendCodeDto) { + return this.auth.registerSendCode(dto); + } + + @Post('register/verify') + registerVerify(@Body() dto: RegisterVerifyDto) { + return this.auth.registerVerify(dto); + } + + @Post('refresh') + refresh(@Body() dto: RefreshTokenDto) { + return this.auth.refresh(dto.refreshToken); + } + + @Post('logout') + logout(@Body() dto: RefreshTokenDto) { + return this.auth.logout(dto.refreshToken); + } + + @Get('me') + @UseGuards(JwtAuthGuard) + me(@CurrentUser() user: AuthUser) { + return this.auth.me(user); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..c44f716 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,33 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { SmsModule } from '../sms/sms.module'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { JwtStrategy } from './strategies/jwt.strategy'; + +@Module({ + imports: [ + SmsModule, + PassportModule.register({ defaultStrategy: 'jwt' }), + JwtModule.registerAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + secret: config.getOrThrow('JWT_ACCESS_SECRET'), + signOptions: { + expiresIn: config.get('JWT_ACCESS_EXPIRES_IN', '15m') as + | `${number}s` + | `${number}m` + | `${number}h` + | `${number}d`, + }, + }), + }), + ], + controllers: [AuthController], + providers: [AuthService, JwtStrategy], + exports: [AuthService, JwtModule], +}) +export class AuthModule {} \ No newline at end of file diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..9a49918 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,425 @@ +import { + BadRequestException, + ConflictException, + ForbiddenException, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { JwtService } from '@nestjs/jwt'; +import { SmsOtp, SmsOtpPurpose, UserRole } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; +import { createHash, randomBytes, randomInt } from 'crypto'; +import { PrismaService } from '../prisma/prisma.service'; +import { SmsService } from '../sms/sms.service'; +import { AuthUser, displayName } from './auth.types'; +import { CellNumberDto } from './dto/cell-number.dto'; +import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto'; +import { LoginDto } from './dto/login.dto'; +import { OtpVerifyDto } from './dto/otp-verify.dto'; +import { RegisterSendCodeDto } from './dto/register-send-code.dto'; +import { RegisterVerifyDto } from './dto/register-verify.dto'; + +const OTP_TTL_MS = 5 * 60 * 1000; +const OTP_RESEND_COOLDOWN_MS = 60 * 1000; +const OTP_MAX_ATTEMPTS = 5; +const OTP_LENGTH = 6; + +type RegisterOtpPayload = { + firstName: string; + lastName: string; + passwordHash: string; +}; + +type OtpSendResult = { + ok: true; + expiresInSeconds: number; + resendAfterSeconds: number; +}; + +@Injectable() +export class AuthService { + constructor( + private readonly prisma: PrismaService, + private readonly jwt: JwtService, + private readonly config: ConfigService, + private readonly sms: SmsService, + ) {} + + async login(dto: LoginDto) { + const user = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + }); + + if (!user) { + throw new UnauthorizedException('شماره یا رمز عبور اشتباه است'); + } + + if (user.disabled) { + throw new ForbiddenException('حساب کاربری غیرفعال است'); + } + + const ok = await bcrypt.compare(dto.password, user.passwordHash); + if (!ok) { + throw new UnauthorizedException('شماره یا رمز عبور اشتباه است'); + } + + return this.issueTokens(user); + } + + async loginSendCode(dto: CellNumberDto): Promise { + const user = await this.requireActiveUser(dto.cellNumber); + return this.createAndSendOtp({ + cellNumber: user.cellNumber, + purpose: SmsOtpPurpose.login, + message: (code) => `کد ورود مادو: ${code}`, + }); + } + + async loginVerify(dto: OtpVerifyDto) { + await this.requireActiveUser(dto.cellNumber); + await this.assertValidOtp({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.login, + code: dto.code, + consume: true, + }); + + const user = await this.prisma.user.findUniqueOrThrow({ + where: { cellNumber: dto.cellNumber }, + }); + if (user.disabled) { + throw new ForbiddenException('حساب کاربری غیرفعال است'); + } + + return this.issueTokens(user); + } + + async forgotPasswordSendCode(dto: CellNumberDto): Promise { + const user = await this.requireActiveUser(dto.cellNumber); + return this.createAndSendOtp({ + cellNumber: user.cellNumber, + purpose: SmsOtpPurpose.resetPassword, + message: (code) => `کد بازیابی رمز مادو: ${code}`, + }); + } + + async forgotPasswordVerify(dto: OtpVerifyDto) { + await this.requireActiveUser(dto.cellNumber); + await this.assertValidOtp({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.resetPassword, + code: dto.code, + consume: false, + }); + return { ok: true as const }; + } + + async forgotPasswordReset(dto: ForgotPasswordResetDto) { + await this.requireActiveUser(dto.cellNumber); + await this.assertValidOtp({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.resetPassword, + code: dto.code, + consume: true, + }); + + const passwordHash = await bcrypt.hash(dto.newPassword, 10); + const user = await this.prisma.user.update({ + where: { cellNumber: dto.cellNumber }, + data: { passwordHash }, + }); + + if (user.disabled) { + throw new ForbiddenException('حساب کاربری غیرفعال است'); + } + + // Invalidate existing sessions after password change + await this.prisma.refreshToken.deleteMany({ where: { userId: user.id } }); + + return this.issueTokens(user); + } + + async registerSendCode(dto: RegisterSendCodeDto): Promise { + const existing = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + select: { id: true }, + }); + if (existing) { + throw new ConflictException('این شماره قبلاً ثبت شده است'); + } + + const passwordHash = await bcrypt.hash(dto.password, 10); + const payload: RegisterOtpPayload = { + firstName: dto.firstName.trim(), + lastName: dto.lastName.trim(), + passwordHash, + }; + + return this.createAndSendOtp({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.register, + payload, + message: (code) => `کد تأیید مادو: ${code}`, + }); + } + + async registerVerify(dto: RegisterVerifyDto) { + const otp = await this.assertValidOtp({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.register, + code: dto.code, + consume: true, + }); + + const payload = otp.payload as RegisterOtpPayload | null; + if ( + !payload?.firstName || + !payload?.lastName || + !payload?.passwordHash + ) { + throw new BadRequestException( + 'اطلاعات ثبت‌نام نامعتبر است. دوباره شروع کنید', + ); + } + + const existing = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + select: { id: true }, + }); + if (existing) { + throw new ConflictException('این شماره قبلاً ثبت شده است'); + } + + const user = await this.prisma.user.create({ + data: { + firstName: payload.firstName, + lastName: payload.lastName, + cellNumber: dto.cellNumber, + passwordHash: payload.passwordHash, + role: UserRole.User, + }, + }); + + return this.issueTokens(user); + } + + async refresh(refreshToken: string) { + const tokenHash = this.hashToken(refreshToken); + const stored = await this.prisma.refreshToken.findFirst({ + where: { tokenHash }, + include: { user: true }, + }); + + if (!stored || stored.expiresAt < new Date()) { + if (stored) { + await this.prisma.refreshToken.delete({ where: { id: stored.id } }); + } + throw new UnauthorizedException('توکن منقضی شده است'); + } + + const user = stored.user; + if (user.disabled) { + throw new UnauthorizedException('نشست نامعتبر است'); + } + + await this.prisma.refreshToken.delete({ where: { id: stored.id } }); + return this.issueTokens(user); + } + + async logout(refreshToken?: string) { + if (!refreshToken) return { ok: true }; + const tokenHash = this.hashToken(refreshToken); + await this.prisma.refreshToken.deleteMany({ where: { tokenHash } }); + return { ok: true }; + } + + async me(actor: AuthUser) { + const user = await this.prisma.user.findUniqueOrThrow({ + where: { id: actor.id }, + }); + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + role: user.role, + name: displayName(user), + }; + } + + private async requireActiveUser(cellNumber: string) { + const user = await this.prisma.user.findUnique({ where: { cellNumber } }); + if (!user) { + throw new BadRequestException('کاربری با این شماره یافت نشد'); + } + if (user.disabled) { + throw new ForbiddenException('حساب کاربری غیرفعال است'); + } + return user; + } + + private async createAndSendOtp(input: { + cellNumber: string; + purpose: SmsOtpPurpose; + payload?: RegisterOtpPayload; + message: (code: string) => string; + }): Promise { + const latest = await this.prisma.smsOtp.findFirst({ + where: { + cellNumber: input.cellNumber, + purpose: input.purpose, + }, + orderBy: { createdAt: 'desc' }, + }); + + if ( + latest && + Date.now() - latest.createdAt.getTime() < OTP_RESEND_COOLDOWN_MS + ) { + const waitSec = Math.ceil( + (OTP_RESEND_COOLDOWN_MS - (Date.now() - latest.createdAt.getTime())) / + 1000, + ); + throw new BadRequestException( + `لطفاً ${waitSec} ثانیه دیگر دوباره تلاش کنید`, + ); + } + + const code = String(randomInt(0, 10 ** OTP_LENGTH)).padStart( + OTP_LENGTH, + '0', + ); + + await this.prisma.smsOtp.deleteMany({ + where: { + cellNumber: input.cellNumber, + purpose: input.purpose, + }, + }); + + await this.prisma.smsOtp.create({ + data: { + cellNumber: input.cellNumber, + purpose: input.purpose, + codeHash: this.hashToken(code), + payload: input.payload ?? undefined, + expiresAt: new Date(Date.now() + OTP_TTL_MS), + }, + }); + + await this.sms.send({ + to: input.cellNumber, + message: input.message(code), + }); + + return { + ok: true, + expiresInSeconds: Math.floor(OTP_TTL_MS / 1000), + resendAfterSeconds: Math.floor(OTP_RESEND_COOLDOWN_MS / 1000), + }; + } + + /** Validates OTP. When consume=true, deletes it on success. */ + private async assertValidOtp(input: { + cellNumber: string; + purpose: SmsOtpPurpose; + code: string; + consume: boolean; + }): Promise { + const otp = await this.prisma.smsOtp.findFirst({ + where: { + cellNumber: input.cellNumber, + purpose: input.purpose, + }, + orderBy: { createdAt: 'desc' }, + }); + + if (!otp || otp.expiresAt < new Date()) { + if (otp) { + await this.prisma.smsOtp.delete({ where: { id: otp.id } }); + } + throw new BadRequestException( + 'کد تأیید منقضی شده است. دوباره درخواست کنید', + ); + } + + if (otp.attempts >= OTP_MAX_ATTEMPTS) { + await this.prisma.smsOtp.delete({ where: { id: otp.id } }); + throw new BadRequestException( + 'تعداد تلاش‌ها بیش از حد مجاز است. دوباره درخواست کنید', + ); + } + + const codeHash = this.hashToken(input.code.trim()); + if (codeHash !== otp.codeHash) { + await this.prisma.smsOtp.update({ + where: { id: otp.id }, + data: { attempts: { increment: 1 } }, + }); + throw new BadRequestException('کد تأیید نادرست است'); + } + + if (input.consume) { + await this.prisma.smsOtp.delete({ where: { id: otp.id } }); + } + + return otp; + } + + private async issueTokens(user: { + id: string; + cellNumber: string; + role: AuthUser['role']; + firstName: string; + lastName: string; + }) { + const accessExpiresIn = this.config.get( + 'JWT_ACCESS_EXPIRES_IN', + '15m', + ); + const accessToken = await this.jwt.signAsync( + { sub: user.id, role: user.role }, + { + secret: this.config.getOrThrow('JWT_ACCESS_SECRET'), + expiresIn: accessExpiresIn as `${number}${'s' | 'm' | 'h' | 'd'}`, + }, + ); + + const refreshToken = randomBytes(48).toString('hex'); + const refreshDays = this.parseDurationDays( + this.config.get('JWT_REFRESH_EXPIRES_IN', '7d'), + ); + + await this.prisma.refreshToken.create({ + data: { + userId: user.id, + tokenHash: this.hashToken(refreshToken), + expiresAt: new Date(Date.now() + refreshDays * 24 * 60 * 60 * 1000), + }, + }); + + return { + accessToken, + refreshToken, + user: { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + role: user.role, + name: displayName(user), + }, + }; + } + + private hashToken(token: string) { + return createHash('sha256').update(token).digest('hex'); + } + + private parseDurationDays(value: string): number { + const match = /^(\d+)d$/i.exec(value.trim()); + return match ? Number(match[1]) : 7; + } +} diff --git a/src/auth/auth.types.ts b/src/auth/auth.types.ts new file mode 100644 index 0000000..d130263 --- /dev/null +++ b/src/auth/auth.types.ts @@ -0,0 +1,20 @@ +import { UserRole } from '@prisma/client'; + +export type AuthUser = { + id: string + cellNumber: string + role: UserRole + firstName: string + lastName: string +} + +export function isElevatedRole(role: UserRole): boolean { + return role === UserRole.Admin || role === UserRole.superAdmin +} + +export function displayName(user: { + firstName: string + lastName: string +}): string { + return `${user.firstName} ${user.lastName}`.trim() +} diff --git a/src/auth/decorators/current-user.decorator.ts b/src/auth/decorators/current-user.decorator.ts new file mode 100644 index 0000000..db774b3 --- /dev/null +++ b/src/auth/decorators/current-user.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { AuthUser } from '../auth.types'; + +export const CurrentUser = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): AuthUser => { + const request = ctx.switchToHttp().getRequest<{ user: AuthUser }>(); + return request.user; + }, +); diff --git a/src/auth/decorators/roles.decorator.ts b/src/auth/decorators/roles.decorator.ts new file mode 100644 index 0000000..ec0c377 --- /dev/null +++ b/src/auth/decorators/roles.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; + +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles); diff --git a/src/auth/dto/cell-number.dto.ts b/src/auth/dto/cell-number.dto.ts new file mode 100644 index 0000000..4808a5d --- /dev/null +++ b/src/auth/dto/cell-number.dto.ts @@ -0,0 +1,7 @@ +import { IsString, Matches } from 'class-validator'; + +export class CellNumberDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; +} diff --git a/src/auth/dto/forgot-password-reset.dto.ts b/src/auth/dto/forgot-password-reset.dto.ts new file mode 100644 index 0000000..289bdf9 --- /dev/null +++ b/src/auth/dto/forgot-password-reset.dto.ts @@ -0,0 +1,16 @@ +import { IsString, Length, Matches, MinLength } from 'class-validator'; + +export class ForgotPasswordResetDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @Length(4, 8, { message: 'کد تأیید نامعتبر است' }) + @Matches(/^\d+$/, { message: 'کد تأیید نامعتبر است' }) + code!: string; + + @IsString() + @MinLength(4, { message: 'رمز عبور باید حداقل ۴ کاراکتر باشد' }) + newPassword!: string; +} diff --git a/src/auth/dto/login.dto.ts b/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..b4bbc11 --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,13 @@ +import { IsString, Matches, MinLength } from 'class-validator'; + +export class LoginDto { + @IsString() + @Matches(/^09\d{9}$/, { + message: 'شماره موبایل معتبر نیست', + }) + cellNumber!: string; + + @IsString() + @MinLength(4) + password!: string; +} diff --git a/src/auth/dto/otp-verify.dto.ts b/src/auth/dto/otp-verify.dto.ts new file mode 100644 index 0000000..e1ec723 --- /dev/null +++ b/src/auth/dto/otp-verify.dto.ts @@ -0,0 +1,12 @@ +import { IsString, Length, Matches } from 'class-validator'; + +export class OtpVerifyDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @Length(4, 8, { message: 'کد تأیید نامعتبر است' }) + @Matches(/^\d+$/, { message: 'کد تأیید نامعتبر است' }) + code!: string; +} diff --git a/src/auth/dto/refresh-token.dto.ts b/src/auth/dto/refresh-token.dto.ts new file mode 100644 index 0000000..179578b --- /dev/null +++ b/src/auth/dto/refresh-token.dto.ts @@ -0,0 +1,7 @@ +import { IsString, MinLength } from 'class-validator'; + +export class RefreshTokenDto { + @IsString() + @MinLength(20) + refreshToken!: string; +} diff --git a/src/auth/dto/register-send-code.dto.ts b/src/auth/dto/register-send-code.dto.ts new file mode 100644 index 0000000..06a23d2 --- /dev/null +++ b/src/auth/dto/register-send-code.dto.ts @@ -0,0 +1,19 @@ +import { IsString, Matches, MinLength } from 'class-validator'; + +export class RegisterSendCodeDto { + @IsString() + @MinLength(1, { message: 'نام الزامی است' }) + firstName!: string; + + @IsString() + @MinLength(1, { message: 'نام خانوادگی الزامی است' }) + lastName!: string; + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @MinLength(4, { message: 'رمز عبور باید حداقل ۴ کاراکتر باشد' }) + password!: string; +} diff --git a/src/auth/dto/register-verify.dto.ts b/src/auth/dto/register-verify.dto.ts new file mode 100644 index 0000000..0dd20b0 --- /dev/null +++ b/src/auth/dto/register-verify.dto.ts @@ -0,0 +1,12 @@ +import { IsString, Matches, Length } from 'class-validator'; + +export class RegisterVerifyDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @Length(4, 8, { message: 'کد تأیید نامعتبر است' }) + @Matches(/^\d+$/, { message: 'کد تأیید نامعتبر است' }) + code!: string; +} diff --git a/src/auth/guards/jwt-auth.guard.ts b/src/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..2155290 --- /dev/null +++ b/src/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/src/auth/guards/roles.guard.ts b/src/auth/guards/roles.guard.ts new file mode 100644 index 0000000..7cfd6be --- /dev/null +++ b/src/auth/guards/roles.guard.ts @@ -0,0 +1,35 @@ +import { + CanActivate, + ExecutionContext, + ForbiddenException, + Injectable, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { UserRole } from '@prisma/client'; +import { ROLES_KEY } from '../decorators/roles.decorator'; +import { AuthUser } from '../auth.types'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private readonly reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride( + ROLES_KEY, + [context.getHandler(), context.getClass()], + ); + + if (!requiredRoles?.length) { + return true; + } + + const request = context.switchToHttp().getRequest<{ user?: AuthUser }>(); + const user = request.user; + + if (!user || !requiredRoles.includes(user.role)) { + throw new ForbiddenException('دسترسی مجاز نیست'); + } + + return true; + } +} diff --git a/src/auth/strategies/jwt.strategy.ts b/src/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..87bc536 --- /dev/null +++ b/src/auth/strategies/jwt.strategy.ts @@ -0,0 +1,44 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { UserRole } from '@prisma/client'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PrismaService } from '../../prisma/prisma.service'; +import { AuthUser } from '../auth.types'; + +type JwtPayload = { + sub: string; + role: UserRole; +}; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor( + config: ConfigService, + private readonly prisma: PrismaService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: config.getOrThrow('JWT_ACCESS_SECRET'), + }); + } + + async validate(payload: JwtPayload): Promise { + const user = await this.prisma.user.findUnique({ + where: { id: payload.sub }, + }); + + if (!user || user.disabled) { + throw new UnauthorizedException('نشست نامعتبر است'); + } + + return { + id: user.id, + cellNumber: user.cellNumber, + role: user.role, + firstName: user.firstName, + lastName: user.lastName, + }; + } +} diff --git a/src/blogs/blogs.controller.ts b/src/blogs/blogs.controller.ts new file mode 100644 index 0000000..6cc7015 --- /dev/null +++ b/src/blogs/blogs.controller.ts @@ -0,0 +1,49 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { BlogsService } from './blogs.service'; +import { CreateBlogDto } from './dto/create-blog.dto'; +import { UpdateBlogDto } from './dto/update-blog.dto'; + +@Controller('blogs') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class BlogsController { + constructor(private readonly blogs: BlogsService) {} + + @Get() + list() { + return this.blogs.list(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.blogs.findOne(id); + } + + @Post() + create(@Body() dto: CreateBlogDto) { + return this.blogs.create(dto); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateBlogDto) { + return this.blogs.update(id, dto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.blogs.remove(id); + } +} diff --git a/src/blogs/blogs.module.ts b/src/blogs/blogs.module.ts new file mode 100644 index 0000000..d31740d --- /dev/null +++ b/src/blogs/blogs.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { BlogsController } from './blogs.controller'; +import { BlogsService } from './blogs.service'; + +@Module({ + controllers: [BlogsController], + providers: [BlogsService], + exports: [BlogsService], +}) +export class BlogsModule {} diff --git a/src/blogs/blogs.service.ts b/src/blogs/blogs.service.ts new file mode 100644 index 0000000..22b4cbd --- /dev/null +++ b/src/blogs/blogs.service.ts @@ -0,0 +1,59 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateBlogDto } from './dto/create-blog.dto'; +import { UpdateBlogDto } from './dto/update-blog.dto'; + +@Injectable() +export class BlogsService { + constructor(private readonly prisma: PrismaService) {} + + list() { + return this.prisma.blogPost.findMany({ + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: string) { + const post = await this.prisma.blogPost.findUnique({ where: { id } }); + if (!post) { + throw new NotFoundException('مقاله یافت نشد'); + } + return post; + } + + create(dto: CreateBlogDto) { + return this.prisma.blogPost.create({ + data: { + title: dto.title, + summary: dto.summary ?? '', + body: dto.body ?? '', + author: dto.author ?? '', + imageUrl: dto.imageUrl, + imageKey: dto.imageKey, + published: dto.published ?? true, + }, + }); + } + + async update(id: string, dto: UpdateBlogDto) { + await this.findOne(id); + return this.prisma.blogPost.update({ + where: { id }, + data: { + ...(dto.title !== undefined ? { title: dto.title } : {}), + ...(dto.summary !== undefined ? { summary: dto.summary } : {}), + ...(dto.body !== undefined ? { body: dto.body } : {}), + ...(dto.author !== undefined ? { author: dto.author } : {}), + ...(dto.imageUrl !== undefined ? { imageUrl: dto.imageUrl } : {}), + ...(dto.imageKey !== undefined ? { imageKey: dto.imageKey } : {}), + ...(dto.published !== undefined ? { published: dto.published } : {}), + }, + }); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.blogPost.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/blogs/dto/create-blog.dto.ts b/src/blogs/dto/create-blog.dto.ts new file mode 100644 index 0000000..01dbd72 --- /dev/null +++ b/src/blogs/dto/create-blog.dto.ts @@ -0,0 +1,31 @@ +import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator'; + +export class CreateBlogDto { + @IsString() + @MinLength(1) + title!: string; + + @IsOptional() + @IsString() + summary?: string; + + @IsOptional() + @IsString() + body?: string; + + @IsOptional() + @IsString() + author?: string; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsString() + imageKey?: string; + + @IsOptional() + @IsBoolean() + published?: boolean; +} diff --git a/src/blogs/dto/update-blog.dto.ts b/src/blogs/dto/update-blog.dto.ts new file mode 100644 index 0000000..b2ddd3f --- /dev/null +++ b/src/blogs/dto/update-blog.dto.ts @@ -0,0 +1,32 @@ +import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator'; + +export class UpdateBlogDto { + @IsOptional() + @IsString() + @MinLength(1) + title?: string; + + @IsOptional() + @IsString() + summary?: string; + + @IsOptional() + @IsString() + body?: string; + + @IsOptional() + @IsString() + author?: string; + + @IsOptional() + @IsString() + imageUrl?: string | null; + + @IsOptional() + @IsString() + imageKey?: string | null; + + @IsOptional() + @IsBoolean() + published?: boolean; +} diff --git a/src/call-requests/call-requests.controller.ts b/src/call-requests/call-requests.controller.ts new file mode 100644 index 0000000..e3920f6 --- /dev/null +++ b/src/call-requests/call-requests.controller.ts @@ -0,0 +1,43 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { CallRequestsService } from './call-requests.service'; +import { CreateCallRequestDto } from './dto/create-call-request.dto'; + +@Controller('call-requests') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class CallRequestsController { + constructor(private readonly callRequests: CallRequestsService) {} + + @Get() + list() { + return this.callRequests.list(); + } + + @Post() + create(@Body() dto: CreateCallRequestDto) { + return this.callRequests.create(dto); + } + + @Patch(':id/handled') + markHandled(@Param('id') id: string) { + return this.callRequests.markHandled(id); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.callRequests.remove(id); + } +} diff --git a/src/call-requests/call-requests.module.ts b/src/call-requests/call-requests.module.ts new file mode 100644 index 0000000..e15a0d6 --- /dev/null +++ b/src/call-requests/call-requests.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { SmsModule } from '../sms/sms.module'; +import { CallRequestsController } from './call-requests.controller'; +import { CallRequestsService } from './call-requests.service'; + +@Module({ + imports: [SmsModule], + controllers: [CallRequestsController], + providers: [CallRequestsService], + exports: [CallRequestsService], +}) +export class CallRequestsModule {} diff --git a/src/call-requests/call-requests.service.ts b/src/call-requests/call-requests.service.ts new file mode 100644 index 0000000..602f0c8 --- /dev/null +++ b/src/call-requests/call-requests.service.ts @@ -0,0 +1,57 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { SmsService } from '../sms/sms.service'; +import { CreateCallRequestDto } from './dto/create-call-request.dto'; + +@Injectable() +export class CallRequestsService { + constructor( + private readonly prisma: PrismaService, + private readonly sms: SmsService, + ) {} + + list() { + return this.prisma.callRequest.findMany({ + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: string) { + const request = await this.prisma.callRequest.findUnique({ where: { id } }); + if (!request) { + throw new NotFoundException('درخواست تماس یافت نشد'); + } + return request; + } + + async create(dto: CreateCallRequestDto) { + const created = await this.prisma.callRequest.create({ + data: { + name: dto.name.trim(), + mobile: dto.mobile, + note: dto.note?.trim() ?? '', + }, + }); + + await this.sms.notifyCallRequest({ + name: created.name, + mobile: created.mobile, + }); + + return created; + } + + async markHandled(id: string) { + await this.findOne(id); + return this.prisma.callRequest.update({ + where: { id }, + data: { handled: true }, + }); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.callRequest.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/call-requests/dto/create-call-request.dto.ts b/src/call-requests/dto/create-call-request.dto.ts new file mode 100644 index 0000000..f2eb4a8 --- /dev/null +++ b/src/call-requests/dto/create-call-request.dto.ts @@ -0,0 +1,15 @@ +import { IsOptional, IsString, Matches, MinLength } from 'class-validator' + +export class CreateCallRequestDto { + @IsString() + @MinLength(1) + name!: string + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + mobile!: string + + @IsOptional() + @IsString() + note?: string +} diff --git a/src/common/districts.ts b/src/common/districts.ts new file mode 100644 index 0000000..99e766b --- /dev/null +++ b/src/common/districts.ts @@ -0,0 +1,29 @@ +/** Districts of Qom used across settings (shipping, branches). */ +export const DISTRICTS = [ + 'پردیسان', + 'جعفریه', + 'زنبیل‌آباد', + 'سالاریه', + 'صفاییه', + 'عطاران', + 'انارستان', + 'بنیاد', + 'آذر', + 'هنرستان', + 'باجک', + 'دورشهر', + 'خیابان ارم', + 'شهرک قدس', + 'شهرک امام حسن', + 'شهرک مهدیه', + 'قلعه کامکار', + 'نیروگاه', + 'حرم', + 'بلوار امین', +] as const; + +export type District = (typeof DISTRICTS)[number]; + +export function isDistrict(value: string): value is District { + return (DISTRICTS as readonly string[]).includes(value); +} diff --git a/src/common/slugify.ts b/src/common/slugify.ts new file mode 100644 index 0000000..7cce7e3 --- /dev/null +++ b/src/common/slugify.ts @@ -0,0 +1,10 @@ +/** Build a URL-safe slug from English (or latin) text. */ +export function slugify(input: string) { + const base = input + .trim() + .toLowerCase() + .replace(/['']/g, '') + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + return base || 'category' +} diff --git a/src/customers/customers.controller.ts b/src/customers/customers.controller.ts new file mode 100644 index 0000000..a345087 --- /dev/null +++ b/src/customers/customers.controller.ts @@ -0,0 +1,49 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { CustomersService } from './customers.service'; +import { CreateCustomerDto } from './dto/create-customer.dto'; +import { UpdateCustomerDto } from './dto/update-customer.dto'; + +@Controller('customers') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class CustomersController { + constructor(private readonly customers: CustomersService) {} + + @Get() + list() { + return this.customers.list(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.customers.findOne(id); + } + + @Post() + create(@Body() dto: CreateCustomerDto) { + return this.customers.create(dto); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateCustomerDto) { + return this.customers.update(id, dto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.customers.remove(id); + } +} diff --git a/src/customers/customers.module.ts b/src/customers/customers.module.ts new file mode 100644 index 0000000..bb9a343 --- /dev/null +++ b/src/customers/customers.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { CustomersController } from './customers.controller'; +import { CustomersService } from './customers.service'; + +@Module({ + controllers: [CustomersController], + providers: [CustomersService], + exports: [CustomersService], +}) +export class CustomersModule {} diff --git a/src/customers/customers.service.ts b/src/customers/customers.service.ts new file mode 100644 index 0000000..4a6c323 --- /dev/null +++ b/src/customers/customers.service.ts @@ -0,0 +1,154 @@ +import { + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateCustomerDto } from './dto/create-customer.dto'; +import { UpdateCustomerDto } from './dto/update-customer.dto'; + +@Injectable() +export class CustomersService { + constructor(private readonly prisma: PrismaService) {} + + private mapCustomer(user: { + id: string; + firstName: string; + lastName: string; + cellNumber: string; + city: string; + disabled: boolean; + ordersCount: number; + createdAt: Date; + updatedAt: Date; + }) { + return { + id: user.id, + name: `${user.firstName} ${user.lastName}`.trim(), + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + city: user.city, + disabled: user.disabled, + ordersCount: user.ordersCount, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }; + } + + async list() { + const users = await this.prisma.user.findMany({ + where: { role: UserRole.User }, + orderBy: { createdAt: 'desc' }, + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + city: true, + disabled: true, + ordersCount: true, + createdAt: true, + updatedAt: true, + }, + }); + return users.map((user) => this.mapCustomer(user)); + } + + async findOne(id: string) { + const user = await this.prisma.user.findFirst({ + where: { id, role: UserRole.User }, + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + city: true, + disabled: true, + ordersCount: true, + createdAt: true, + updatedAt: true, + }, + }); + if (!user) { + throw new NotFoundException('مشتری یافت نشد'); + } + return this.mapCustomer(user); + } + + async create(dto: CreateCustomerDto) { + const existing = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + select: { id: true }, + }); + if (existing) { + throw new ConflictException('این شماره قبلاً ثبت شده است'); + } + + const passwordHash = await bcrypt.hash(dto.password, 10); + const user = await this.prisma.user.create({ + data: { + firstName: dto.firstName.trim(), + lastName: dto.lastName.trim(), + cellNumber: dto.cellNumber, + city: dto.city?.trim() ?? '', + passwordHash, + role: UserRole.User, + }, + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + city: true, + disabled: true, + ordersCount: true, + createdAt: true, + updatedAt: true, + }, + }); + return this.mapCustomer(user); + } + + async update(id: string, dto: UpdateCustomerDto) { + await this.findOne(id); + + let firstName: string | undefined; + let lastName: string | undefined; + if (dto.name !== undefined) { + const parts = dto.name.trim().split(/\s+/); + firstName = parts[0] ?? ''; + lastName = parts.slice(1).join(' '); + } + + const user = await this.prisma.user.update({ + where: { id }, + data: { + ...(firstName !== undefined ? { firstName } : {}), + ...(lastName !== undefined ? { lastName } : {}), + ...(dto.city !== undefined ? { city: dto.city } : {}), + ...(dto.disabled !== undefined ? { disabled: dto.disabled } : {}), + }, + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + city: true, + disabled: true, + ordersCount: true, + createdAt: true, + updatedAt: true, + }, + }); + return this.mapCustomer(user); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.user.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/customers/dto/create-customer.dto.ts b/src/customers/dto/create-customer.dto.ts new file mode 100644 index 0000000..5967b20 --- /dev/null +++ b/src/customers/dto/create-customer.dto.ts @@ -0,0 +1,28 @@ +import { + IsOptional, + IsString, + Matches, + MinLength, +} from 'class-validator' + +export class CreateCustomerDto { + @IsString() + @MinLength(1) + firstName!: string + + @IsString() + @MinLength(1) + lastName!: string + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string + + @IsOptional() + @IsString() + city?: string + + @IsString() + @MinLength(4) + password!: string +} diff --git a/src/customers/dto/update-customer.dto.ts b/src/customers/dto/update-customer.dto.ts new file mode 100644 index 0000000..a39e7d6 --- /dev/null +++ b/src/customers/dto/update-customer.dto.ts @@ -0,0 +1,16 @@ +import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator'; + +export class UpdateCustomerDto { + @IsOptional() + @IsString() + @MinLength(1) + name?: string; + + @IsOptional() + @IsString() + city?: string; + + @IsOptional() + @IsBoolean() + disabled?: boolean; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..d93508a --- /dev/null +++ b/src/main.ts @@ -0,0 +1,28 @@ +import { ValidationPipe } from '@nestjs/common' +import { NestFactory } from '@nestjs/core' +import { AppModule } from './app.module' + +async function bootstrap() { + const app = await NestFactory.create(AppModule) + + app.setGlobalPrefix('api/v1') + app.enableCors({ + origin: process.env.CORS_ORIGIN?.split(',').map((s) => s.trim()) ?? true, + credentials: true, + }) + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { enableImplicitConversion: true }, + }), + ) + + const port = Number(process.env.PORT ?? 3000) + await app.listen(port) + // eslint-disable-next-line no-console + console.log(`Mado Motor API listening on http://localhost:${port}/api/v1`) +} + +void bootstrap() diff --git a/src/media/dto/media-upload-query.dto.ts b/src/media/dto/media-upload-query.dto.ts new file mode 100644 index 0000000..ff13040 --- /dev/null +++ b/src/media/dto/media-upload-query.dto.ts @@ -0,0 +1,9 @@ +import { IsIn } from 'class-validator'; + +export const MEDIA_KINDS = ['main', 'gallery', 'blog', 'temp'] as const; +export type MediaKind = (typeof MEDIA_KINDS)[number]; + +export class MediaUploadQueryDto { + @IsIn(MEDIA_KINDS, { message: 'نوع فایل نامعتبر است' }) + kind!: MediaKind; +} diff --git a/src/media/media.controller.ts b/src/media/media.controller.ts new file mode 100644 index 0000000..4913bed --- /dev/null +++ b/src/media/media.controller.ts @@ -0,0 +1,36 @@ +import { + Controller, + Post, + Query, + UploadedFile, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { UserRole } from '@prisma/client'; +import { memoryStorage } from 'multer'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { MediaUploadQueryDto } from './dto/media-upload-query.dto'; +import { MediaService } from './media.service'; + +@Controller('media') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class MediaController { + constructor(private readonly media: MediaService) {} + + @Post('upload') + @UseInterceptors( + FileInterceptor('file', { + storage: memoryStorage(), + }), + ) + upload( + @Query() query: MediaUploadQueryDto, + @UploadedFile() file: Express.Multer.File, + ) { + return this.media.upload(query.kind, file); + } +} diff --git a/src/media/media.module.ts b/src/media/media.module.ts new file mode 100644 index 0000000..dc7392c --- /dev/null +++ b/src/media/media.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { StorageModule } from '../storage/storage.module'; +import { MediaController } from './media.controller'; +import { MediaService } from './media.service'; + +@Module({ + imports: [ConfigModule, StorageModule], + controllers: [MediaController], + providers: [MediaService], +}) +export class MediaModule {} diff --git a/src/media/media.service.ts b/src/media/media.service.ts new file mode 100644 index 0000000..a827250 --- /dev/null +++ b/src/media/media.service.ts @@ -0,0 +1,85 @@ +import { + BadRequestException, + Injectable, + PayloadTooLargeException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { randomUUID } from 'crypto'; +import { extname } from 'path'; +import { + productGalleryKey, + productMainKey, + blogMediaKey, + tempMediaKey, +} from '../storage/storage-keys'; +import { StorageService } from '../storage/storage.service'; +import { MediaKind } from './dto/media-upload-query.dto'; + +const ALLOWED_MIME_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']); + +const MIME_EXTENSIONS: Record = { + 'image/jpeg': '.jpg', + 'image/png': '.png', + 'image/webp': '.webp', +}; + +@Injectable() +export class MediaService { + constructor( + private readonly storage: StorageService, + private readonly config: ConfigService, + ) {} + + async upload(kind: MediaKind, file: Express.Multer.File) { + if (!file) { + throw new BadRequestException('فایل ارسال نشده است'); + } + + if (!ALLOWED_MIME_TYPES.has(file.mimetype)) { + throw new BadRequestException('فرمت تصویر مجاز نیست'); + } + + const maxMb = Number( + this.config.get('MEDIA_MAX_FILE_SIZE_MB', '10'), + ); + const maxBytes = maxMb * 1024 * 1024; + + if (file.size > maxBytes) { + throw new PayloadTooLargeException( + `حجم فایل نباید بیشتر از ${maxMb} مگابایت باشد`, + ); + } + + const ext = + MIME_EXTENSIONS[file.mimetype] ?? extname(file.originalname) ?? ''; + const fileName = `${randomUUID()}${ext}`; + const key = this.resolveKey(kind, fileName); + + const stored = await this.storage.upload({ + key, + body: file.buffer, + contentType: file.mimetype, + }); + + return { + url: stored.publicUrl, + storageKey: stored.storagePath, + storageDisk: stored.storageDisk, + }; + } + + private resolveKey(kind: MediaKind, fileName: string): string { + switch (kind) { + case 'main': + return productMainKey(fileName); + case 'gallery': + return productGalleryKey(fileName); + case 'blog': + return blogMediaKey(fileName); + case 'temp': + return tempMediaKey(fileName); + default: + throw new BadRequestException('نوع فایل نامعتبر است'); + } + } +} diff --git a/src/orders/dto/update-order-status.dto.ts b/src/orders/dto/update-order-status.dto.ts new file mode 100644 index 0000000..8a65893 --- /dev/null +++ b/src/orders/dto/update-order-status.dto.ts @@ -0,0 +1,7 @@ +import { OrderStatus } from '@prisma/client'; +import { IsEnum } from 'class-validator'; + +export class UpdateOrderStatusDto { + @IsEnum(OrderStatus) + status!: OrderStatus; +} diff --git a/src/orders/orders.controller.ts b/src/orders/orders.controller.ts new file mode 100644 index 0000000..4a8d916 --- /dev/null +++ b/src/orders/orders.controller.ts @@ -0,0 +1,40 @@ +import { + Body, + Controller, + Get, + Param, + ParseIntPipe, + Patch, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { UpdateOrderStatusDto } from './dto/update-order-status.dto'; +import { OrdersService } from './orders.service'; + +@Controller('orders') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class OrdersController { + constructor(private readonly orders: OrdersService) {} + + @Get() + list() { + return this.orders.list(); + } + + @Get(':id') + findOne(@Param('id', ParseIntPipe) id: number) { + return this.orders.findOne(id); + } + + @Patch(':id/status') + updateStatus( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateOrderStatusDto, + ) { + return this.orders.updateStatus(id, dto); + } +} diff --git a/src/orders/orders.module.ts b/src/orders/orders.module.ts new file mode 100644 index 0000000..6b429b9 --- /dev/null +++ b/src/orders/orders.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { OrdersController } from './orders.controller'; +import { OrdersService } from './orders.service'; + +@Module({ + controllers: [OrdersController], + providers: [OrdersService], + exports: [OrdersService], +}) +export class OrdersModule {} diff --git a/src/orders/orders.service.ts b/src/orders/orders.service.ts new file mode 100644 index 0000000..1847973 --- /dev/null +++ b/src/orders/orders.service.ts @@ -0,0 +1,43 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { UpdateOrderStatusDto } from './dto/update-order-status.dto'; + +@Injectable() +export class OrdersService { + constructor(private readonly prisma: PrismaService) {} + + list() { + return this.prisma.order.findMany({ + orderBy: { createdAt: 'desc' }, + select: { + id: true, + customerName: true, + customerPhone: true, + itemCount: true, + status: true, + createdAt: true, + items: true, + }, + }); + } + + async findOne(id: number) { + const order = await this.prisma.order.findUnique({ + where: { id }, + include: { items: true }, + }); + if (!order) { + throw new NotFoundException('سفارش یافت نشد'); + } + return order; + } + + async updateStatus(id: number, dto: UpdateOrderStatusDto) { + await this.findOne(id); + return this.prisma.order.update({ + where: { id }, + data: { status: dto.status }, + include: { items: true }, + }); + } +} diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts new file mode 100644 index 0000000..7207426 --- /dev/null +++ b/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts new file mode 100644 index 0000000..7ffd32d --- /dev/null +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,16 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } +} diff --git a/src/products/dto/create-product.dto.ts b/src/products/dto/create-product.dto.ts new file mode 100644 index 0000000..a1525f3 --- /dev/null +++ b/src/products/dto/create-product.dto.ts @@ -0,0 +1,32 @@ +import { IsInt, IsOptional, IsString, Min, MinLength } from 'class-validator'; + +export class CreateProductDto { + @IsString() + @MinLength(1) + nameFa!: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsInt() + @Min(0) + price!: number; + + @IsOptional() + @IsInt() + @Min(0) + stock?: number; + + @IsOptional() + @IsString() + category?: string; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsString() + imageKey?: string; +} diff --git a/src/products/dto/update-product.dto.ts b/src/products/dto/update-product.dto.ts new file mode 100644 index 0000000..610c596 --- /dev/null +++ b/src/products/dto/update-product.dto.ts @@ -0,0 +1,34 @@ +import { IsInt, IsOptional, IsString, Min, MinLength } from 'class-validator'; + +export class UpdateProductDto { + @IsOptional() + @IsString() + @MinLength(1) + nameFa?: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsInt() + @Min(0) + price?: number; + + @IsOptional() + @IsInt() + @Min(0) + stock?: number; + + @IsOptional() + @IsString() + category?: string; + + @IsOptional() + @IsString() + imageUrl?: string | null; + + @IsOptional() + @IsString() + imageKey?: string | null; +} diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts new file mode 100644 index 0000000..c736b74 --- /dev/null +++ b/src/products/products.controller.ts @@ -0,0 +1,49 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { CreateProductDto } from './dto/create-product.dto'; +import { UpdateProductDto } from './dto/update-product.dto'; +import { ProductsService } from './products.service'; + +@Controller('products') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class ProductsController { + constructor(private readonly products: ProductsService) {} + + @Get() + list() { + return this.products.list(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.products.findOne(id); + } + + @Post() + create(@Body() dto: CreateProductDto) { + return this.products.create(dto); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateProductDto) { + return this.products.update(id, dto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.products.remove(id); + } +} diff --git a/src/products/products.module.ts b/src/products/products.module.ts new file mode 100644 index 0000000..883c79f --- /dev/null +++ b/src/products/products.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ProductsController } from './products.controller'; +import { ProductsService } from './products.service'; + +@Module({ + controllers: [ProductsController], + providers: [ProductsService], + exports: [ProductsService], +}) +export class ProductsModule {} diff --git a/src/products/products.service.ts b/src/products/products.service.ts new file mode 100644 index 0000000..525fd27 --- /dev/null +++ b/src/products/products.service.ts @@ -0,0 +1,59 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateProductDto } from './dto/create-product.dto'; +import { UpdateProductDto } from './dto/update-product.dto'; + +@Injectable() +export class ProductsService { + constructor(private readonly prisma: PrismaService) {} + + list() { + return this.prisma.product.findMany({ + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: string) { + const product = await this.prisma.product.findUnique({ where: { id } }); + if (!product) { + throw new NotFoundException('محصول یافت نشد'); + } + return product; + } + + create(dto: CreateProductDto) { + return this.prisma.product.create({ + data: { + nameFa: dto.nameFa, + nameEn: dto.nameEn ?? '', + price: dto.price, + stock: dto.stock ?? 0, + category: dto.category ?? '', + imageUrl: dto.imageUrl, + imageKey: dto.imageKey, + }, + }); + } + + async update(id: string, dto: UpdateProductDto) { + await this.findOne(id); + return this.prisma.product.update({ + where: { id }, + data: { + ...(dto.nameFa !== undefined ? { nameFa: dto.nameFa } : {}), + ...(dto.nameEn !== undefined ? { nameEn: dto.nameEn } : {}), + ...(dto.price !== undefined ? { price: dto.price } : {}), + ...(dto.stock !== undefined ? { stock: dto.stock } : {}), + ...(dto.category !== undefined ? { category: dto.category } : {}), + ...(dto.imageUrl !== undefined ? { imageUrl: dto.imageUrl } : {}), + ...(dto.imageKey !== undefined ? { imageKey: dto.imageKey } : {}), + }, + }); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.product.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/public/dto/public-representation-request.dto.ts b/src/public/dto/public-representation-request.dto.ts new file mode 100644 index 0000000..7c8d46c --- /dev/null +++ b/src/public/dto/public-representation-request.dto.ts @@ -0,0 +1,43 @@ +import { IsOptional, IsString, Matches, MinLength } from 'class-validator' + +export class PublicRepresentationRequestDto { + @IsString() + @MinLength(1) + firstName!: string + + @IsString() + @MinLength(1) + lastName!: string + + @IsOptional() + @IsString() + nationalCode?: string + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + mobile!: string + + @IsOptional() + @IsString() + province?: string + + @IsString() + @MinLength(1) + city!: string + + @IsOptional() + @IsString() + landline?: string + + @IsOptional() + @IsString() + address?: string + + @IsOptional() + @IsString() + postalCode?: string + + @IsOptional() + @IsString() + description?: string +} diff --git a/src/public/public.controller.ts b/src/public/public.controller.ts new file mode 100644 index 0000000..d14400e --- /dev/null +++ b/src/public/public.controller.ts @@ -0,0 +1,160 @@ +import { Body, Controller, Get, NotFoundException, Param, Post } from '@nestjs/common' +import { RepresentationStatus } from '@prisma/client' +import { CreateCallRequestDto } from '../call-requests/dto/create-call-request.dto' +import { PrismaService } from '../prisma/prisma.service' +import { SmsService } from '../sms/sms.service' +import { PublicRepresentationRequestDto } from './dto/public-representation-request.dto' + +@Controller('public') +export class PublicController { + constructor( + private readonly prisma: PrismaService, + private readonly sms: SmsService, + ) {} + + @Get('products') + listProducts() { + return this.prisma.product.findMany({ + orderBy: { createdAt: 'desc' }, + select: { + id: true, + nameFa: true, + nameEn: true, + price: true, + stock: true, + category: true, + imageUrl: true, + createdAt: true, + }, + }) + } + + @Get('products/:id') + async getProduct(@Param('id') id: string) { + const product = await this.prisma.product.findUnique({ + where: { id }, + select: { + id: true, + nameFa: true, + nameEn: true, + price: true, + stock: true, + category: true, + imageUrl: true, + createdAt: true, + updatedAt: true, + }, + }) + if (!product) { + throw new NotFoundException('محصول یافت نشد') + } + return product + } + + @Get('blogs') + listBlogs() { + return this.prisma.blogPost.findMany({ + where: { published: true }, + orderBy: { createdAt: 'desc' }, + select: { + id: true, + title: true, + summary: true, + author: true, + imageUrl: true, + createdAt: true, + }, + }) + } + + @Get('blogs/:id') + async getBlog(@Param('id') id: string) { + const post = await this.prisma.blogPost.findFirst({ + where: { id, published: true }, + }) + if (!post) { + throw new NotFoundException('مطلب یافت نشد') + } + return post + } + + @Get('representations') + listActiveRepresentations() { + return this.prisma.representation.findMany({ + where: { status: RepresentationStatus.active }, + orderBy: [{ city: 'asc' }, { createdAt: 'desc' }], + select: { + id: true, + name: true, + owner: true, + mobile: true, + province: true, + city: true, + address: true, + landline: true, + createdAt: true, + }, + }) + } + + @Post('representations/request') + async createRepresentationRequest(@Body() dto: PublicRepresentationRequestDto) { + const firstName = dto.firstName.trim() + const lastName = dto.lastName.trim() + const name = `${firstName} ${lastName}`.trim() + const created = await this.prisma.representation.create({ + data: { + name, + firstName, + lastName, + nationalCode: dto.nationalCode?.trim() ?? '', + mobile: dto.mobile, + province: dto.province?.trim() ?? '', + city: dto.city.trim(), + landline: dto.landline?.trim() ?? '', + address: dto.address?.trim() ?? '', + postalCode: dto.postalCode?.trim() ?? '', + description: dto.description?.trim() ?? '', + owner: '', + status: RepresentationStatus.pending, + }, + select: { + id: true, + name: true, + city: true, + status: true, + createdAt: true, + }, + }) + + await this.sms.notifyRepresentationRequest({ + name, + mobile: dto.mobile, + city: dto.city, + }) + + return created + } + + @Post('call-requests') + async createCallRequest(@Body() dto: CreateCallRequestDto) { + const created = await this.prisma.callRequest.create({ + data: { + name: dto.name.trim(), + mobile: dto.mobile, + note: dto.note?.trim() ?? '', + }, + }) + + await this.sms.notifyCallRequest({ + name: created.name, + mobile: created.mobile, + }) + + return { + id: created.id, + name: created.name, + createdAt: created.createdAt, + } + } +} diff --git a/src/public/public.module.ts b/src/public/public.module.ts new file mode 100644 index 0000000..e67ab07 --- /dev/null +++ b/src/public/public.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common' +import { PrismaModule } from '../prisma/prisma.module' +import { SmsModule } from '../sms/sms.module' +import { PublicController } from './public.controller' + +@Module({ + imports: [PrismaModule, SmsModule], + controllers: [PublicController], +}) +export class PublicModule {} diff --git a/src/representations/dto/create-representation.dto.ts b/src/representations/dto/create-representation.dto.ts new file mode 100644 index 0000000..df8ae06 --- /dev/null +++ b/src/representations/dto/create-representation.dto.ts @@ -0,0 +1,23 @@ +import { IsOptional, IsString, Matches, MinLength } from 'class-validator' + +export class CreateRepresentationDto { + @IsString() + @MinLength(1) + name!: string + + @IsOptional() + @IsString() + owner?: string + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + mobile!: string + + @IsString() + @MinLength(1) + city!: string + + @IsOptional() + @IsString() + address?: string +} diff --git a/src/representations/dto/list-representations-query.dto.ts b/src/representations/dto/list-representations-query.dto.ts new file mode 100644 index 0000000..5793dfe --- /dev/null +++ b/src/representations/dto/list-representations-query.dto.ts @@ -0,0 +1,8 @@ +import { RepresentationStatus } from '@prisma/client'; +import { IsEnum, IsOptional } from 'class-validator'; + +export class ListRepresentationsQueryDto { + @IsOptional() + @IsEnum(RepresentationStatus) + status?: RepresentationStatus; +} diff --git a/src/representations/dto/update-representation.dto.ts b/src/representations/dto/update-representation.dto.ts new file mode 100644 index 0000000..067df3e --- /dev/null +++ b/src/representations/dto/update-representation.dto.ts @@ -0,0 +1,26 @@ +import { IsOptional, IsString, MinLength } from 'class-validator'; + +export class UpdateRepresentationDto { + @IsOptional() + @IsString() + @MinLength(1) + name?: string; + + @IsOptional() + @IsString() + owner?: string; + + @IsOptional() + @IsString() + @MinLength(1) + mobile?: string; + + @IsOptional() + @IsString() + @MinLength(1) + city?: string; + + @IsOptional() + @IsString() + address?: string; +} diff --git a/src/representations/representations.controller.ts b/src/representations/representations.controller.ts new file mode 100644 index 0000000..6762c09 --- /dev/null +++ b/src/representations/representations.controller.ts @@ -0,0 +1,61 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { RepresentationStatus, UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { CreateRepresentationDto } from './dto/create-representation.dto'; +import { ListRepresentationsQueryDto } from './dto/list-representations-query.dto'; +import { UpdateRepresentationDto } from './dto/update-representation.dto'; +import { RepresentationsService } from './representations.service'; + +@Controller('representations') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class RepresentationsController { + constructor(private readonly representations: RepresentationsService) {} + + @Get() + list(@Query() query: ListRepresentationsQueryDto) { + return this.representations.list(query); + } + + @Post() + create(@Body() dto: CreateRepresentationDto) { + return this.representations.create(dto, RepresentationStatus.pending); + } + + @Post('active') + createActive(@Body() dto: CreateRepresentationDto) { + return this.representations.create(dto, RepresentationStatus.active); + } + + @Post(':id/approve') + approve(@Param('id') id: string) { + return this.representations.approve(id); + } + + @Post(':id/reject') + reject(@Param('id') id: string) { + return this.representations.reject(id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateRepresentationDto) { + return this.representations.update(id, dto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.representations.remove(id); + } +} diff --git a/src/representations/representations.module.ts b/src/representations/representations.module.ts new file mode 100644 index 0000000..c18ff67 --- /dev/null +++ b/src/representations/representations.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { RepresentationsController } from './representations.controller'; +import { RepresentationsService } from './representations.service'; + +@Module({ + controllers: [RepresentationsController], + providers: [RepresentationsService], + exports: [RepresentationsService], +}) +export class RepresentationsModule {} diff --git a/src/representations/representations.service.ts b/src/representations/representations.service.ts new file mode 100644 index 0000000..9ff88b6 --- /dev/null +++ b/src/representations/representations.service.ts @@ -0,0 +1,77 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { RepresentationStatus } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateRepresentationDto } from './dto/create-representation.dto'; +import { ListRepresentationsQueryDto } from './dto/list-representations-query.dto'; +import { UpdateRepresentationDto } from './dto/update-representation.dto'; + +@Injectable() +export class RepresentationsService { + constructor(private readonly prisma: PrismaService) {} + + list(query: ListRepresentationsQueryDto) { + return this.prisma.representation.findMany({ + where: query.status ? { status: query.status } : undefined, + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: string) { + const representation = await this.prisma.representation.findUnique({ + where: { id }, + }); + if (!representation) { + throw new NotFoundException('نمایندگی یافت نشد'); + } + return representation; + } + + create(dto: CreateRepresentationDto, status: RepresentationStatus = RepresentationStatus.pending) { + return this.prisma.representation.create({ + data: { + name: dto.name.trim(), + owner: dto.owner?.trim() ?? '', + mobile: dto.mobile, + city: dto.city.trim(), + address: dto.address?.trim() ?? '', + status, + }, + }); + } + + async approve(id: string) { + await this.findOne(id); + return this.prisma.representation.update({ + where: { id }, + data: { status: RepresentationStatus.active }, + }); + } + + async reject(id: string) { + await this.findOne(id); + return this.prisma.representation.update({ + where: { id }, + data: { status: RepresentationStatus.rejected }, + }); + } + + async update(id: string, dto: UpdateRepresentationDto) { + await this.findOne(id); + return this.prisma.representation.update({ + where: { id }, + data: { + ...(dto.name !== undefined ? { name: dto.name } : {}), + ...(dto.owner !== undefined ? { owner: dto.owner } : {}), + ...(dto.mobile !== undefined ? { mobile: dto.mobile } : {}), + ...(dto.city !== undefined ? { city: dto.city } : {}), + ...(dto.address !== undefined ? { address: dto.address } : {}), + }, + }); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.representation.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/reviews/reviews.controller.ts b/src/reviews/reviews.controller.ts new file mode 100644 index 0000000..bb77580 --- /dev/null +++ b/src/reviews/reviews.controller.ts @@ -0,0 +1,40 @@ +import { + Controller, + Delete, + Get, + Param, + Post, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { ReviewsService } from './reviews.service'; + +@Controller('reviews') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.Admin, UserRole.superAdmin) +export class ReviewsController { + constructor(private readonly reviews: ReviewsService) {} + + @Get() + list() { + return this.reviews.list(); + } + + @Post(':id/approve') + approve(@Param('id') id: string) { + return this.reviews.approve(id); + } + + @Post(':id/reject') + reject(@Param('id') id: string) { + return this.reviews.reject(id); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.reviews.remove(id); + } +} diff --git a/src/reviews/reviews.module.ts b/src/reviews/reviews.module.ts new file mode 100644 index 0000000..ae7faa7 --- /dev/null +++ b/src/reviews/reviews.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ReviewsController } from './reviews.controller'; +import { ReviewsService } from './reviews.service'; + +@Module({ + controllers: [ReviewsController], + providers: [ReviewsService], + exports: [ReviewsService], +}) +export class ReviewsModule {} diff --git a/src/reviews/reviews.service.ts b/src/reviews/reviews.service.ts new file mode 100644 index 0000000..baf7083 --- /dev/null +++ b/src/reviews/reviews.service.ts @@ -0,0 +1,44 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { ReviewStatus } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class ReviewsService { + constructor(private readonly prisma: PrismaService) {} + + list() { + return this.prisma.review.findMany({ + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: string) { + const review = await this.prisma.review.findUnique({ where: { id } }); + if (!review) { + throw new NotFoundException('نظر یافت نشد'); + } + return review; + } + + async approve(id: string) { + await this.findOne(id); + return this.prisma.review.update({ + where: { id }, + data: { status: ReviewStatus.approved }, + }); + } + + async reject(id: string) { + await this.findOne(id); + return this.prisma.review.update({ + where: { id }, + data: { status: ReviewStatus.rejected }, + }); + } + + async remove(id: string) { + await this.findOne(id); + await this.prisma.review.delete({ where: { id } }); + return { ok: true }; + } +} diff --git a/src/sitemap/sitemap.controller.ts b/src/sitemap/sitemap.controller.ts new file mode 100644 index 0000000..296f672 --- /dev/null +++ b/src/sitemap/sitemap.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, Header } from '@nestjs/common' +import { SitemapService } from './sitemap.service' + +@Controller() +export class SitemapController { + constructor(private readonly sitemap: SitemapService) {} + + @Get(['sitemap.xml', 'sitemap']) + @Header('Content-Type', 'application/xml; charset=utf-8') + @Header('Cache-Control', 'public, max-age=300') + getSitemap() { + return this.sitemap.buildXml() + } +} diff --git a/src/sitemap/sitemap.module.ts b/src/sitemap/sitemap.module.ts new file mode 100644 index 0000000..eaeca4e --- /dev/null +++ b/src/sitemap/sitemap.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common' +import { PrismaModule } from '../prisma/prisma.module' +import { SitemapController } from './sitemap.controller' +import { SitemapService } from './sitemap.service' + +@Module({ + imports: [PrismaModule], + controllers: [SitemapController], + providers: [SitemapService], +}) +export class SitemapModule {} diff --git a/src/sitemap/sitemap.service.ts b/src/sitemap/sitemap.service.ts new file mode 100644 index 0000000..092a275 --- /dev/null +++ b/src/sitemap/sitemap.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common' +import { PrismaService } from '../prisma/prisma.service' + +const SITE_ORIGIN = (process.env.PUBLIC_SITE_ORIGIN ?? 'https://madomotor.ir').replace(/\/$/, '') + +const STATIC_PATHS: { path: string; changefreq: string; priority: string }[] = [ + { path: '/', changefreq: 'daily', priority: '1.0' }, + { path: '/product/list/محصولات', changefreq: 'daily', priority: '0.9' }, + { path: '/blog/list/اخبار-و-مقالات', changefreq: 'daily', priority: '0.9' }, + { path: '/representation/list/نمایندگی-ها', changefreq: 'weekly', priority: '0.8' }, + { path: '/representation/request', changefreq: 'monthly', priority: '0.6' }, + { path: '/contactus/تماس-با-ما', changefreq: 'monthly', priority: '0.7' }, + { path: '/aboutus/درباره-ما', changefreq: 'monthly', priority: '0.7' }, + { path: '/landing/mado-230/موتور-برقی-مادو-۲۳۰', changefreq: 'weekly', priority: '0.8' }, + { path: '/landing/mado-130/موتور-برقی-مادو-۱۳۰', changefreq: 'weekly', priority: '0.8' }, + { path: '/landing/mado-330/موتور-برقی-مادو-۳۳۰', changefreq: 'weekly', priority: '0.8' }, +] + +@Injectable() +export class SitemapService { + constructor(private readonly prisma: PrismaService) {} + + async buildXml() { + const [products, blogs] = await Promise.all([ + this.prisma.product.findMany({ + select: { id: true, nameFa: true, updatedAt: true }, + orderBy: { updatedAt: 'desc' }, + }), + this.prisma.blogPost.findMany({ + where: { published: true }, + select: { id: true, title: true, updatedAt: true }, + orderBy: { updatedAt: 'desc' }, + }), + ]) + + const latestProduct = products[0]?.updatedAt + const latestBlog = blogs[0]?.updatedAt + + const urls: string[] = [] + + for (const page of STATIC_PATHS) { + let lastmod: Date | undefined + if (page.path.startsWith('/product/list') && latestProduct) lastmod = latestProduct + if (page.path.startsWith('/blog/list') && latestBlog) lastmod = latestBlog + urls.push(urlEntry(page.path, lastmod, page.changefreq, page.priority)) + } + + for (const product of products) { + urls.push( + urlEntry( + `/product/${product.id}/${toSlug(product.nameFa)}`, + product.updatedAt, + 'weekly', + '0.7', + ), + ) + } + + for (const post of blogs) { + urls.push( + urlEntry(`/blog/${post.id}/${toSlug(post.title)}`, post.updatedAt, 'weekly', '0.7'), + ) + } + + return ` + +${urls.join('\n')} + +` + } +} + +function toSlug(value: string) { + return value + .trim() + .replace(/\s+/g, '-') + .replace(/[^\u0600-\u06FFa-zA-Z0-9-]/g, '') +} + +function escapeXml(value: string) { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +function urlEntry(path: string, lastmod: Date | undefined, changefreq: string, priority: string) { + const loc = escapeXml(encodeURI(`${SITE_ORIGIN}${path}`)) + const lastmodTag = lastmod + ? `\n ${lastmod.toISOString().slice(0, 10)}` + : '' + return ` + ${loc}${lastmodTag} + ${changefreq} + ${priority} + ` +} diff --git a/src/sms/sms.module.ts b/src/sms/sms.module.ts new file mode 100644 index 0000000..cc282dd --- /dev/null +++ b/src/sms/sms.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { SmsService } from './sms.service'; + +@Module({ + providers: [SmsService], + exports: [SmsService], +}) +export class SmsModule {} diff --git a/src/sms/sms.service.ts b/src/sms/sms.service.ts new file mode 100644 index 0000000..b2854a1 --- /dev/null +++ b/src/sms/sms.service.ts @@ -0,0 +1,120 @@ +import { + Injectable, + InternalServerErrorException, + Logger, + ServiceUnavailableException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { SendSmsInput, SendSmsResult } from './sms.types'; + +@Injectable() +export class SmsService { + private readonly logger = new Logger(SmsService.name); + private readonly apiUrl: string; + private readonly apiKey: string; + private readonly domain: string; + + constructor(private readonly config: ConfigService) { + this.apiUrl = this.config.getOrThrow('MESHKEE_SMS_URL'); + this.apiKey = this.config.getOrThrow('MESHKEE_SMS_API_KEY'); + this.domain = this.config.getOrThrow('MESHKEE_SMS_DOMAIN'); + } + + notifyMobiles(): string[] { + const raw = this.config.get('SMS_NOTIFY_MOBILES', ''); + return raw + .split(/[,\s]+/) + .map((item) => item.trim()) + .filter((item) => /^09\d{9}$/.test(item)); + } + + async sendQuietly(input: SendSmsInput): Promise { + try { + return await this.send(input); + } catch (err) { + this.logger.error( + `Meshkee SMS skipped for ${input.to}: ${err instanceof Error ? err.message : String(err)}`, + ); + return null; + } + } + + async notifyAdmins(message: string, except?: string) { + const targets = this.notifyMobiles().filter((to) => to !== except); + await Promise.all( + targets.map((to) => this.sendQuietly({ to, message })), + ); + } + + async notifyCallRequest(input: { name: string; mobile: string }) { + const name = input.name.trim(); + await this.sendQuietly({ + to: input.mobile, + message: `${name} عزیز\nدرخواست شما ثبت شد.\nبه زودی با شما تماس خواهیم گرفت.\nمادو موتور.`, + }); + await this.notifyAdmins( + `کاربر ${name} با شماره ${input.mobile} درخواست تماس ثبت کرده است.\nمادوموتور.`, + input.mobile, + ); + } + + async notifyRepresentationRequest(input: { + name: string; + mobile: string; + city: string; + }) { + const name = input.name.trim(); + await this.sendQuietly({ + to: input.mobile, + message: `${name} عزیز\nدرخواست نمایندگی شما ثبت شد.\nبه زودی با شما تماس خواهیم گرفت.\nمادو موتور.`, + }); + await this.notifyAdmins( + `درخواست نمایندگی جدید از ${name} با شماره ${input.mobile} در ${input.city.trim()} ثبت شد.\nمادوموتور.`, + input.mobile, + ); + } + + async send(input: SendSmsInput): Promise { + const to = input.to.trim(); + const message = input.message.trim(); + + if (!/^09\d{9}$/.test(to)) { + throw new InternalServerErrorException('Invalid SMS destination'); + } + if (!message) { + throw new InternalServerErrorException('SMS message is empty'); + } + + let response: Response; + try { + response = await fetch(this.apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': this.apiKey, + }, + body: JSON.stringify({ + domain: this.domain, + to, + message, + }), + }); + } catch (err) { + this.logger.error('Meshkee SMS request failed', err); + throw new ServiceUnavailableException('SMS provider unreachable'); + } + + const body = (await response.json().catch(() => null)) as + | { success?: boolean; serverId?: string; message?: string } + | null; + + if (!response.ok || !body?.success || !body.serverId) { + this.logger.error( + `Meshkee SMS rejected: status=${response.status} body=${JSON.stringify(body)}`, + ); + throw new ServiceUnavailableException('SMS send failed'); + } + + return { success: true, serverId: body.serverId }; + } +} diff --git a/src/sms/sms.types.ts b/src/sms/sms.types.ts new file mode 100644 index 0000000..a3533ec --- /dev/null +++ b/src/sms/sms.types.ts @@ -0,0 +1,9 @@ +export type SendSmsInput = { + to: string; + message: string; +}; + +export type SendSmsResult = { + success: true; + serverId: string; +}; diff --git a/src/storage/s3-storage.driver.ts b/src/storage/s3-storage.driver.ts new file mode 100644 index 0000000..fa08447 --- /dev/null +++ b/src/storage/s3-storage.driver.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + DeleteObjectCommand, + GetObjectCommand, + PutObjectCommand, + S3Client, +} from '@aws-sdk/client-s3'; +import { StoredObject, UploadObjectInput } from './storage.types'; + +@Injectable() +export class S3StorageDriver { + private readonly client: S3Client; + private readonly bucket: string; + private readonly publicUrlBase: string; + private readonly storageDisk = 's3'; + + constructor(private readonly config: ConfigService) { + const endpoint = this.config.getOrThrow('S3_ENDPOINT'); + const region = this.config.get('S3_REGION', 'us-east-1'); + const forcePathStyle = + this.config.get('S3_FORCE_PATH_STYLE', 'true') === 'true'; + + this.bucket = this.config.getOrThrow('S3_BUCKET'); + this.publicUrlBase = this.config + .getOrThrow('S3_PUBLIC_URL') + .replace(/\/$/, ''); + + this.client = new S3Client({ + endpoint, + region, + forcePathStyle, + credentials: { + accessKeyId: this.config.getOrThrow('S3_ACCESS_KEY_ID'), + secretAccessKey: this.config.getOrThrow('S3_SECRET_ACCESS_KEY'), + }, + }); + } + + async upload(input: UploadObjectInput): Promise { + const key = input.key.replace(/^\/+/, ''); + + await this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + Body: input.body, + ContentType: input.contentType, + }), + ); + + return { + storageDisk: this.storageDisk, + storagePath: key, + publicUrl: `${this.publicUrlBase}/${key}`, + }; + } + + async getBuffer(storagePath: string): Promise { + const key = storagePath.replace(/^\/+/, ''); + const result = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + }), + ); + + if (!result.Body) { + throw new Error(`Empty object body for ${key}`); + } + + return Buffer.from(await result.Body.transformToByteArray()); + } + + async delete(storagePath: string): Promise { + const key = storagePath.replace(/^\/+/, ''); + + await this.client.send( + new DeleteObjectCommand({ + Bucket: this.bucket, + Key: key, + }), + ); + } +} diff --git a/src/storage/storage-keys.ts b/src/storage/storage-keys.ts new file mode 100644 index 0000000..2d7ad48 --- /dev/null +++ b/src/storage/storage-keys.ts @@ -0,0 +1,31 @@ +/** + * Parspack S3 object key layout (same bucket as Balout/Meshkee): + * + * mado-motor/ + * products/main/{uuid}{ext} + * products/gallery/{uuid}{ext} + * blogs/{uuid}{ext} + * users/{uuid}{ext} + * temp/{uuid}{ext} + */ +const ROOT = 'mado-motor'; + +export function productMainKey(fileName: string): string { + return `${ROOT}/products/main/${fileName}`; +} + +export function productGalleryKey(fileName: string): string { + return `${ROOT}/products/gallery/${fileName}`; +} + +export function blogMediaKey(fileName: string): string { + return `${ROOT}/blogs/${fileName}`; +} + +export function userMediaKey(fileName: string): string { + return `${ROOT}/users/${fileName}`; +} + +export function tempMediaKey(fileName: string): string { + return `${ROOT}/temp/${fileName}`; +} diff --git a/src/storage/storage.module.ts b/src/storage/storage.module.ts new file mode 100644 index 0000000..8e6072d --- /dev/null +++ b/src/storage/storage.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { S3StorageDriver } from './s3-storage.driver'; +import { StorageService } from './storage.service'; + +@Module({ + providers: [S3StorageDriver, StorageService], + exports: [StorageService], +}) +export class StorageModule {} diff --git a/src/storage/storage.service.ts b/src/storage/storage.service.ts new file mode 100644 index 0000000..e8abfc6 --- /dev/null +++ b/src/storage/storage.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { S3StorageDriver } from './s3-storage.driver'; +import { StoredObject, UploadObjectInput } from './storage.types'; + +@Injectable() +export class StorageService { + constructor(private readonly s3: S3StorageDriver) {} + + upload(input: UploadObjectInput): Promise { + return this.s3.upload(input); + } + + getBuffer(storagePath: string): Promise { + return this.s3.getBuffer(storagePath); + } + + delete(storagePath: string): Promise { + return this.s3.delete(storagePath); + } +} diff --git a/src/storage/storage.types.ts b/src/storage/storage.types.ts new file mode 100644 index 0000000..efdd02d --- /dev/null +++ b/src/storage/storage.types.ts @@ -0,0 +1,11 @@ +export type StoredObject = { + storageDisk: string; + storagePath: string; + publicUrl: string; +}; + +export type UploadObjectInput = { + key: string; + body: Buffer; + contentType: string; +}; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..57f9635 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "resolvePackageJsonExports": true, + "esModuleInterop": true, + "isolatedModules": true, + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "noFallthroughCasesInSwitch": true + } +}