From fe78a99b7873ee1f1d13a0bdf33b25b0f0e5cb23 Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Mon, 7 Sep 2026 20:49:55 +0330 Subject: [PATCH] Initial commit: Mana Collection NestJS backend. Co-authored-by: Cursor --- .env.example | 39 + .gitignore | 8 + CONTEXT.md | 34 + README.md | 61 + docker-compose.yml | 25 + nest-cli.json | 8 + package-lock.json | 5601 +++++++++++++++++ package.json | 52 + .../20260907120000_init/migration.sql | 361 ++ .../migration.sql | 5 + prisma/migrations/migration_lock.toml | 2 + prisma/schema.prisma | 287 + scripts/create-super-admin.ts | 63 + scripts/seed-mana-store.ts | 114 + scripts/send-sms.ts | 53 + src/app.module.ts | 39 + src/auth/auth.controller.ts | 73 + src/auth/auth.module.ts | 33 + src/auth/auth.service.ts | 488 ++ src/auth/auth.types.ts | 25 + 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 | 11 + 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 | 47 + src/blogs/blogs.controller.ts | 58 + src/blogs/blogs.module.ts | 11 + src/blogs/blogs.service.ts | 103 + src/blogs/dto/blog.dto.ts | 95 + src/brands/brands.controller.ts | 53 + src/brands/brands.module.ts | 11 + src/brands/brands.service.ts | 79 + src/brands/dto/brand.dto.ts | 42 + src/catalog/catalog.controller.ts | 60 + src/catalog/catalog.module.ts | 7 + src/categories/categories.controller.ts | 53 + src/categories/categories.module.ts | 11 + src/categories/categories.service.ts | 107 + src/categories/dto/category.dto.ts | 81 + src/common/slugify.ts | 9 + src/common/store-scope.service.ts | 76 + src/customers/customers.controller.ts | 20 + src/customers/customers.module.ts | 10 + src/customers/customers.service.ts | 91 + src/main.ts | 28 + src/media/dto/media-upload-query.dto.ts | 17 + src/media/media.controller.ts | 36 + src/media/media.module.ts | 12 + src/media/media.service.ts | 94 + src/orders/dto/order.dto.ts | 43 + src/orders/orders.controller.ts | 56 + src/orders/orders.module.ts | 10 + src/orders/orders.service.ts | 130 + src/prisma/prisma.module.ts | 9 + src/prisma/prisma.service.ts | 16 + src/products/dto/create-product.dto.ts | 181 + src/products/products.controller.ts | 71 + src/products/products.module.ts | 11 + src/products/products.service.ts | 190 + src/profile/dto/profile.dto.ts | 80 + src/profile/profile.controller.ts | 62 + src/profile/profile.module.ts | 9 + src/profile/profile.service.ts | 123 + src/public/public.controller.ts | 41 + src/public/public.module.ts | 9 + src/public/public.service.ts | 113 + src/sms/sms.module.ts | 8 + src/sms/sms.service.ts | 92 + src/sms/sms.types.ts | 9 + src/storage/s3-storage.driver.ts | 85 + src/storage/storage-keys.ts | 41 + src/storage/storage.module.ts | 9 + src/storage/storage.service.ts | 20 + src/storage/storage.types.ts | 11 + src/stores/dto/store.dto.ts | 88 + src/stores/stores.controller.ts | 66 + src/stores/stores.module.ts | 14 + src/stores/stores.service.ts | 211 + tsconfig.build.json | 4 + tsconfig.json | 25 + 87 files changed, 10557 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 CONTEXT.md create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 nest-cli.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 prisma/migrations/20260907120000_init/migration.sql create mode 100644 prisma/migrations/20260907163000_product_details/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/seed-mana-store.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/blog.dto.ts create mode 100644 src/brands/brands.controller.ts create mode 100644 src/brands/brands.module.ts create mode 100644 src/brands/brands.service.ts create mode 100644 src/brands/dto/brand.dto.ts create mode 100644 src/catalog/catalog.controller.ts create mode 100644 src/catalog/catalog.module.ts create mode 100644 src/categories/categories.controller.ts create mode 100644 src/categories/categories.module.ts create mode 100644 src/categories/categories.service.ts create mode 100644 src/categories/dto/category.dto.ts create mode 100644 src/common/slugify.ts create mode 100644 src/common/store-scope.service.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/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/order.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/products.controller.ts create mode 100644 src/products/products.module.ts create mode 100644 src/products/products.service.ts create mode 100644 src/profile/dto/profile.dto.ts create mode 100644 src/profile/profile.controller.ts create mode 100644 src/profile/profile.module.ts create mode 100644 src/profile/profile.service.ts create mode 100644 src/public/public.controller.ts create mode 100644 src/public/public.module.ts create mode 100644 src/public/public.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 src/stores/dto/store.dto.ts create mode 100644 src/stores/stores.controller.ts create mode 100644 src/stores/stores.module.ts create mode 100644 src/stores/stores.service.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..96a0d37 --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# PostgreSQL (Docker Compose) +POSTGRES_HOST=localhost +POSTGRES_PORT=5436 +POSTGRES_USER=mana +POSTGRES_PASSWORD=mana_secret +POSTGRES_DB=mana_collection + +DATABASE_URL=postgresql://mana:mana_secret@localhost:5436/mana_collection + +# API +PORT=3002 +PUBLIC_SITE_ORIGIN=http://mana-collection.com:3000 +CORS_ORIGIN=http://localhost:3000,http://127.0.0.1:3000,http://mana-collection.com:3000,http://www.mana-collection.com:3000,http://store.mana-collection.com:3000,http://customer.mana-collection.com:3000,http://admin.mana-collection.com:3000,https://manacollections.ir,https://store.manacollections.ir,https://customer.manacollections.ir,https://admin.manacollections.ir + +# JWT +JWT_ACCESS_SECRET=change-me-mana-access-secret-min-32-chars +JWT_REFRESH_SECRET=change-me-mana-refresh-secret-min-32-chars +JWT_ACCESS_EXPIRES_IN=15m +JWT_REFRESH_EXPIRES_IN=7d + +# Object storage (same Parspack bucket as Mado/Balout — prefix mana-collection/) +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 manacollections.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=manacollections.ir +# Comma-separated admin mobiles for alerts +SMS_NOTIFY_MOBILES= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..643a356 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +.env +*.log +.DS_Store +scripts/data/ + +.idea/ diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..43ff355 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,34 @@ +# Mana Collection Backend — Context + +## Product + +Multi-seller fashion marketplace. Each seller is a `Store` with one `STORE_ADMIN` owner. + +Future dashboards: + +- `store.manacollections.ir` — store admin +- `customer.manacollections.ir` — customer +- `admin.manacollections.ir` — super admin + +## Patterns (from Mado Motor) + +- Nest 11, Prisma 6, bcrypt, passport-jwt +- Meshkee SMS (`MESHKEE_SMS_*`) +- Parspack S3 with project prefix `mana-collection/` (Mado uses `mado-motor/`) +- Opaque refresh tokens (hashed in DB), JWT access with `{ sub, role, storeId? }` +- OTP: SHA-256 hash, 6 digits, 5 min TTL, 60s resend, 5 attempts + +## Store lifecycle + +1. Public `storeRegister` OTP → creates `User(STORE_ADMIN)` + `Store(pending)` +2. Super admin sets status `active` (or `rejected` with reason) +3. Pending store: only `GET/PATCH /stores/me` +4. Active store: full products/categories/brands/blogs/orders/customers CRUD + +## Multi-store orders + +`Order` can span multiple stores via `OrderItem.storeId` and per-store `OrderStoreFulfillment`. Store admins see only their items + update their fulfillment status. Super admin manages overall `Order.status`. + +## Catalog variants + +Categories hold `colors[]`, `sizes[]`, `packages[]` (catalog ids). Products have nested `ProductVariant` rows with `colorId` / `sizeId` / `packageId` + prices. Static catalogs exposed at `/catalog`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f967064 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Mana Collection API + +NestJS 11 + Prisma 6 + PostgreSQL multi-seller marketplace backend for **Mana Collection**. + +- Public site: https://manacollections.ir +- API prefix: `/api/v1` +- Default port: `3002` +- Postgres Docker port: `5436` +- S3 prefix: `mana-collection/` +- SMS domain: `manacollections.ir` + +## Quick start + +```bash +cp .env.example .env +# fill JWT secrets, S3 keys, MESHKEE_SMS_API_KEY + +npm install +npm run db:up +npx prisma migrate deploy +npx prisma generate + +npm run seed:mana-store -- --phone 09120000000 --password mana1234 +# optional super admin: +# npm run seed:mana-store -- --phone 09120000000 --password mana1234 --super-phone 09127004945 --super-password admin1234 + +npm run start:dev +``` + +API: http://localhost:3002/api/v1 + +## Scripts + +| Script | Purpose | +|--------|---------| +| `npm run db:up` | Start Postgres (port 5436) | +| `npm run prisma:migrate` | Dev migrations | +| `npm run prisma:deploy` | Apply migrations | +| `npm run create-super-admin` | Create `SUPER_ADMIN` | +| `npm run seed:mana-store` | Seed Mana Collection store + `STORE_ADMIN` | +| `npm run send-sms` | Send Meshkee SMS | + +## Roles + +- `CUSTOMER` — public registration +- `STORE_ADMIN` — owns one store; full catalog CRUD only when store is `active` +- `SUPER_ADMIN` — approve stores, all orders, all customers + +## Key routes + +- Auth: `POST /auth/login`, `/auth/register/*`, `/auth/refresh`, `GET /auth/me` +- Store register: `POST /public/stores/register/send-code`, `/verify` +- Store me: `GET|PATCH /stores/me` +- Admin stores: `GET /admin/stores`, `PATCH /admin/stores/:id/status` +- Products / categories / brands / blogs / customers / orders (JWT + role) +- Public: `GET /public/products`, `/public/products/:id`, `/public/stores`, `/public/blogs` +- Profile: `GET|PATCH /profile`, addresses CRUD +- Catalog: `GET /catalog` (colors / sizes / packages) +- Media: `POST /media/upload?kind=main|gallery|blog|brand|store|avatar|temp` + +See `CONTEXT.md` for domain notes. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..26ff0bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + postgres: + image: postgres:16-alpine + container_name: mana-postgres + restart: unless-stopped + ports: + - "127.0.0.1:${POSTGRES_PORT:-5436}:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER:-mana} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mana_secret} + POSTGRES_DB: ${POSTGRES_DB:-mana_collection} + volumes: + - mana_postgres_data:/var/lib/postgresql/data + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-mana} -d ${POSTGRES_DB:-mana_collection}", + ] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + mana_postgres_data: 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..44e2b17 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5601 @@ +{ + "name": "mana-collection-api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mana-collection-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", + "dotenv": "^16.4.7", + "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.1127.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1127.0.tgz", + "integrity": "sha512-0ZSAgmEda33xPqVPt+bx2KzrXV1cUCKRRVPGliLu+V7DzHPa04CqPSi1maGEM4O0LDP0iI6HRSW5ULloNwayNw==", + "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/config/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/@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.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "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.18.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", + "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", + "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.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "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.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "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.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "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/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": "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/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/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.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", + "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..d4787c4 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "mana-collection-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", + "seed:mana-store": "ts-node -r tsconfig-paths/register scripts/seed-mana-store.ts", + "send-sms": "ts-node -r tsconfig-paths/register scripts/send-sms.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", + "dotenv": "^16.4.7", + "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/20260907120000_init/migration.sql b/prisma/migrations/20260907120000_init/migration.sql new file mode 100644 index 0000000..dd893bc --- /dev/null +++ b/prisma/migrations/20260907120000_init/migration.sql @@ -0,0 +1,361 @@ +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('CUSTOMER', 'STORE_ADMIN', 'SUPER_ADMIN'); + +-- CreateEnum +CREATE TYPE "StoreStatus" AS ENUM ('pending', 'active', 'rejected', 'suspended'); + +-- CreateEnum +CREATE TYPE "OrderStatus" AS ENUM ('pending', 'confirmed', 'preparing', 'shipped', 'delivered', 'cancelled'); + +-- CreateEnum +CREATE TYPE "OrderStoreStatus" AS ENUM ('pending', 'confirmed', 'preparing', 'shipped', 'delivered', 'cancelled'); + +-- CreateEnum +CREATE TYPE "SmsOtpPurpose" AS ENUM ('register', 'login', 'resetPassword', 'storeRegister'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "firstName" TEXT NOT NULL, + "lastName" TEXT NOT NULL, + "cellNumber" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "email" TEXT, + "instagram" TEXT, + "about" TEXT, + "avatarUrl" TEXT, + "avatarKey" TEXT, + "role" "UserRole" NOT NULL DEFAULT 'CUSTOMER', + "disabled" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UserAddress" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "label" TEXT NOT NULL DEFAULT '', + "province" TEXT NOT NULL, + "city" TEXT NOT NULL, + "fullAddress" TEXT NOT NULL, + "landline" TEXT, + + CONSTRAINT "UserAddress_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 "Store" ( + "id" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "nameFa" TEXT NOT NULL, + "nameEn" TEXT NOT NULL DEFAULT '', + "description" TEXT, + "logoUrl" TEXT, + "logoKey" TEXT, + "status" "StoreStatus" NOT NULL DEFAULT 'pending', + "ownerId" TEXT NOT NULL, + "rejectionReason" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Store_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Category" ( + "id" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "parentId" TEXT, + "nameFa" TEXT NOT NULL, + "nameEn" TEXT NOT NULL DEFAULT '', + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "colors" TEXT[] DEFAULT ARRAY[]::TEXT[], + "sizes" TEXT[] DEFAULT ARRAY[]::TEXT[], + "packages" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "Category_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Brand" ( + "id" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "nameFa" TEXT NOT NULL, + "nameEn" TEXT NOT NULL DEFAULT '', + "imageUrl" TEXT, + "imageKey" TEXT, + + CONSTRAINT "Brand_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Product" ( + "id" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "categoryId" TEXT, + "brandId" TEXT, + "nameFa" TEXT NOT NULL, + "nameEn" TEXT NOT NULL DEFAULT '', + "price" INTEGER NOT NULL DEFAULT 0, + "discountPrice" INTEGER, + "imageUrl" TEXT, + "imageKey" TEXT, + "tags" TEXT[] DEFAULT ARRAY[]::TEXT[], + "published" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Product_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ProductVariant" ( + "id" TEXT NOT NULL, + "productId" TEXT NOT NULL, + "colorId" TEXT NOT NULL DEFAULT '', + "sizeId" TEXT NOT NULL DEFAULT '', + "packageId" TEXT NOT NULL DEFAULT '', + "price" INTEGER NOT NULL DEFAULT 0, + "discountPrice" INTEGER, + + CONSTRAINT "ProductVariant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BlogPost" ( + "id" TEXT NOT NULL, + "storeId" 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, + "metaTitle" TEXT, + "metaDescription" TEXT, + "slug" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "BlogPost_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Order" ( + "id" TEXT NOT NULL, + "customerId" TEXT, + "customerName" TEXT NOT NULL, + "customerPhone" TEXT NOT NULL, + "status" "OrderStatus" NOT NULL DEFAULT 'pending', + "itemCount" INTEGER NOT NULL DEFAULT 0, + "totalAmount" INTEGER NOT NULL DEFAULT 0, + "note" TEXT NOT NULL DEFAULT '', + "shippingAddress" JSONB, + "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" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "productId" TEXT, + "nameFa" TEXT NOT NULL, + "variantLabel" TEXT, + "quantity" INTEGER NOT NULL DEFAULT 1, + "unitPrice" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OrderStoreFulfillment" ( + "id" TEXT NOT NULL, + "orderId" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "status" "OrderStoreStatus" NOT NULL DEFAULT 'pending', + + CONSTRAINT "OrderStoreFulfillment_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 "UserAddress_userId_idx" ON "UserAddress"("userId"); + +-- 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 UNIQUE INDEX "Store_slug_key" ON "Store"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Store_ownerId_key" ON "Store"("ownerId"); + +-- CreateIndex +CREATE INDEX "Store_status_idx" ON "Store"("status"); + +-- CreateIndex +CREATE INDEX "Category_storeId_idx" ON "Category"("storeId"); + +-- CreateIndex +CREATE INDEX "Category_parentId_idx" ON "Category"("parentId"); + +-- CreateIndex +CREATE INDEX "Category_sortOrder_idx" ON "Category"("sortOrder"); + +-- CreateIndex +CREATE INDEX "Brand_storeId_idx" ON "Brand"("storeId"); + +-- CreateIndex +CREATE INDEX "Product_storeId_idx" ON "Product"("storeId"); + +-- CreateIndex +CREATE INDEX "Product_categoryId_idx" ON "Product"("categoryId"); + +-- CreateIndex +CREATE INDEX "Product_brandId_idx" ON "Product"("brandId"); + +-- CreateIndex +CREATE INDEX "Product_price_idx" ON "Product"("price"); + +-- CreateIndex +CREATE INDEX "Product_published_idx" ON "Product"("published"); + +-- CreateIndex +CREATE INDEX "ProductVariant_productId_idx" ON "ProductVariant"("productId"); + +-- CreateIndex +CREATE INDEX "BlogPost_storeId_idx" ON "BlogPost"("storeId"); + +-- CreateIndex +CREATE INDEX "BlogPost_published_idx" ON "BlogPost"("published"); + +-- CreateIndex +CREATE INDEX "BlogPost_slug_idx" ON "BlogPost"("slug"); + +-- 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_storeId_idx" ON "OrderItem"("storeId"); + +-- CreateIndex +CREATE INDEX "OrderItem_productId_idx" ON "OrderItem"("productId"); + +-- CreateIndex +CREATE INDEX "OrderStoreFulfillment_storeId_idx" ON "OrderStoreFulfillment"("storeId"); + +-- CreateIndex +CREATE INDEX "OrderStoreFulfillment_status_idx" ON "OrderStoreFulfillment"("status"); + +-- CreateIndex +CREATE UNIQUE INDEX "OrderStoreFulfillment_orderId_storeId_key" ON "OrderStoreFulfillment"("orderId", "storeId"); + +-- AddForeignKey +ALTER TABLE "UserAddress" ADD CONSTRAINT "UserAddress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Store" ADD CONSTRAINT "Store_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Category" ADD CONSTRAINT "Category_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Category" ADD CONSTRAINT "Category_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Brand" ADD CONSTRAINT "Brand_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_brandId_fkey" FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductVariant" ADD CONSTRAINT "ProductVariant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BlogPost" ADD CONSTRAINT "BlogPost_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("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_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE RESTRICT 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 "OrderStoreFulfillment" ADD CONSTRAINT "OrderStoreFulfillment_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderStoreFulfillment" ADD CONSTRAINT "OrderStoreFulfillment_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20260907163000_product_details/migration.sql b/prisma/migrations/20260907163000_product_details/migration.sql new file mode 100644 index 0000000..e2febbe --- /dev/null +++ b/prisma/migrations/20260907163000_product_details/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "summary" TEXT NOT NULL DEFAULT ''; +ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "description" TEXT NOT NULL DEFAULT ''; +ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "galleryUrls" TEXT[] DEFAULT ARRAY[]::TEXT[]; +ALTER TABLE "Product" ADD COLUMN IF NOT EXISTS "galleryKeys" TEXT[] DEFAULT ARRAY[]::TEXT[]; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..690c93e --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,2 @@ +-- Prisma migration lock +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..8fc1dc6 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,287 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum UserRole { + CUSTOMER + STORE_ADMIN + SUPER_ADMIN +} + +enum StoreStatus { + pending + active + rejected + suspended +} + +enum OrderStatus { + pending + confirmed + preparing + shipped + delivered + cancelled +} + +enum OrderStoreStatus { + pending + confirmed + preparing + shipped + delivered + cancelled +} + +enum SmsOtpPurpose { + register + login + resetPassword + storeRegister +} + +model User { + id String @id @default(cuid()) + firstName String + lastName String + cellNumber String @unique + passwordHash String + email String? + instagram String? + about String? + avatarUrl String? + avatarKey String? + role UserRole @default(CUSTOMER) + disabled Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + refreshTokens RefreshToken[] + addresses UserAddress[] + ownedStore Store? + orders Order[] @relation("CustomerOrders") + + @@index([role]) + @@index([disabled]) +} + +model UserAddress { + id String @id @default(cuid()) + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + label String @default("") + province String + city String + fullAddress String + landline String? + + @@index([userId]) +} + +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 Store { + id String @id @default(cuid()) + slug String @unique + nameFa String + nameEn String @default("") + description String? + logoUrl String? + logoKey String? + status StoreStatus @default(pending) + ownerId String @unique + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + rejectionReason String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + products Product[] + categories Category[] + brands Brand[] + blogs BlogPost[] + orderItems OrderItem[] + fulfillments OrderStoreFulfillment[] + + @@index([status]) +} + +model Category { + id String @id @default(cuid()) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + parentId String? + parent Category? @relation("CategoryTree", fields: [parentId], references: [id], onDelete: SetNull) + children Category[] @relation("CategoryTree") + nameFa String + nameEn String @default("") + sortOrder Int @default(0) + colors String[] @default([]) + sizes String[] @default([]) + packages String[] @default([]) + products Product[] + + @@index([storeId]) + @@index([parentId]) + @@index([sortOrder]) +} + +model Brand { + id String @id @default(cuid()) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + nameFa String + nameEn String @default("") + imageUrl String? + imageKey String? + products Product[] + + @@index([storeId]) +} + +model Product { + id String @id @default(cuid()) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + categoryId String? + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + brandId String? + brand Brand? @relation(fields: [brandId], references: [id], onDelete: SetNull) + nameFa String + nameEn String @default("") + summary String @default("") + description String @default("") + price Int @default(0) + discountPrice Int? + imageUrl String? + imageKey String? + galleryUrls String[] @default([]) + galleryKeys String[] @default([]) + tags String[] @default([]) + published Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + variants ProductVariant[] + orderItems OrderItem[] + + @@index([storeId]) + @@index([categoryId]) + @@index([brandId]) + @@index([price]) + @@index([published]) +} + +model ProductVariant { + id String @id @default(cuid()) + productId String + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + colorId String @default("") + sizeId String @default("") + packageId String @default("") + price Int @default(0) + discountPrice Int? + + @@index([productId]) +} + +model BlogPost { + id String @id @default(cuid()) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + title String + summary String @default("") + body String @default("") + author String @default("") + imageUrl String? + imageKey String? + published Boolean @default(true) + metaTitle String? + metaDescription String? + slug String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([storeId]) + @@index([published]) + @@index([slug]) +} + +model Order { + id String @id @default(cuid()) + customerId String? + customer User? @relation("CustomerOrders", fields: [customerId], references: [id], onDelete: SetNull) + customerName String + customerPhone String + status OrderStatus @default(pending) + itemCount Int @default(0) + totalAmount Int @default(0) + note String @default("") + shippingAddress Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + items OrderItem[] + fulfillments OrderStoreFulfillment[] + + @@index([customerId]) + @@index([status]) + @@index([createdAt]) +} + +model OrderItem { + id String @id @default(cuid()) + orderId String + order Order @relation(fields: [orderId], references: [id], onDelete: Cascade) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Restrict) + productId String? + product Product? @relation(fields: [productId], references: [id], onDelete: SetNull) + nameFa String + variantLabel String? + quantity Int @default(1) + unitPrice Int @default(0) + + @@index([orderId]) + @@index([storeId]) + @@index([productId]) +} + +model OrderStoreFulfillment { + id String @id @default(cuid()) + orderId String + order Order @relation(fields: [orderId], references: [id], onDelete: Cascade) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Restrict) + status OrderStoreStatus @default(pending) + + @@unique([orderId, storeId]) + @@index([storeId]) + @@index([status]) +} diff --git a/scripts/create-super-admin.ts b/scripts/create-super-admin.ts new file mode 100644 index 0000000..82e2aaf --- /dev/null +++ b/scripts/create-super-admin.ts @@ -0,0 +1,63 @@ +/** + * Create the first SUPER_ADMIN. + * + * 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.SUPER_ADMIN, + }, + }); + + console.log('Created SUPER_ADMIN:', { + 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/seed-mana-store.ts b/scripts/seed-mana-store.ts new file mode 100644 index 0000000..69d333f --- /dev/null +++ b/scripts/seed-mana-store.ts @@ -0,0 +1,114 @@ +/** + * Seed Mana Collection flagship store + STORE_ADMIN (and optional SUPER_ADMIN). + * + * Usage: + * npm run seed:mana-store -- \ + * --phone 09120000000 --password secret123 \ + * --first مانا --last ادمین \ + * [--super-phone 09127004945 --super-password secret123] + */ +import { PrismaClient, StoreStatus, UserRole } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; + +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]) return process.argv[idx + 1]; + return fallback; +} + +async function main() { + const cellNumber = arg('phone', '09120000000')!; + const password = arg('password', 'mana1234')!; + const firstName = arg('first', 'مانا')!; + const lastName = arg('last', 'ادمین')!; + const superPhone = arg('super-phone'); + const superPassword = arg('super-password', 'admin1234'); + + if (!/^09\d{9}$/.test(cellNumber)) { + throw new Error('phone must match 09xxxxxxxxx'); + } + + const passwordHash = await bcrypt.hash(password, 10); + + const storeAdmin = await prisma.user.upsert({ + where: { cellNumber }, + update: { + firstName, + lastName, + passwordHash, + role: UserRole.STORE_ADMIN, + disabled: false, + }, + create: { + firstName, + lastName, + cellNumber, + passwordHash, + role: UserRole.STORE_ADMIN, + }, + }); + + const store = await prisma.store.upsert({ + where: { slug: 'mana-collection' }, + update: { + nameFa: 'مانا کالکشن', + nameEn: 'Mana Collection', + status: StoreStatus.active, + ownerId: storeAdmin.id, + rejectionReason: null, + }, + create: { + slug: 'mana-collection', + nameFa: 'مانا کالکشن', + nameEn: 'Mana Collection', + description: 'فروشگاه اصلی مانا کالکشن', + status: StoreStatus.active, + ownerId: storeAdmin.id, + }, + }); + + console.log('Seeded STORE_ADMIN + store:', { + userId: storeAdmin.id, + cellNumber: storeAdmin.cellNumber, + storeId: store.id, + slug: store.slug, + status: store.status, + }); + + if (superPhone) { + if (!/^09\d{9}$/.test(superPhone)) { + throw new Error('super-phone must match 09xxxxxxxxx'); + } + const superHash = await bcrypt.hash(superPassword!, 10); + const superAdmin = await prisma.user.upsert({ + where: { cellNumber: superPhone }, + update: { + role: UserRole.SUPER_ADMIN, + passwordHash: superHash, + disabled: false, + }, + create: { + firstName: 'سوپر', + lastName: 'ادمین', + cellNumber: superPhone, + passwordHash: superHash, + role: UserRole.SUPER_ADMIN, + }, + }); + console.log('Seeded SUPER_ADMIN:', { + id: superAdmin.id, + cellNumber: superAdmin.cellNumber, + }); + } +} + +main() + .catch((err) => { + console.error(err.message || err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/send-sms.ts b/scripts/send-sms.ts new file mode 100644 index 0000000..462cc6d --- /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 ?? 'manacollections.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..f69ba2c --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,39 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { AuthModule } from './auth/auth.module'; +import { BlogsModule } from './blogs/blogs.module'; +import { BrandsModule } from './brands/brands.module'; +import { CatalogModule } from './catalog/catalog.module'; +import { CategoriesModule } from './categories/categories.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 { ProfileModule } from './profile/profile.module'; +import { PublicModule } from './public/public.module'; +import { SmsModule } from './sms/sms.module'; +import { StorageModule } from './storage/storage.module'; +import { StoresModule } from './stores/stores.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + PrismaModule, + StorageModule, + SmsModule, + AuthModule, + MediaModule, + PublicModule, + CatalogModule, + StoresModule, + ProductsModule, + CategoriesModule, + BrandsModule, + BlogsModule, + CustomersModule, + OrdersModule, + ProfileModule, + ], +}) +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..b1a0d6f --- /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 {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..eca73ee --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,488 @@ +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 }, + include: { ownedStore: { select: { id: true } } }, + }); + + 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 }, + include: { ownedStore: { select: { id: true } } }, + }); + 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 }, + include: { ownedStore: { select: { id: true } } }, + }); + + if (user.disabled) { + throw new ForbiddenException('حساب کاربری غیرفعال است'); + } + + 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.CUSTOMER, + }, + include: { ownedStore: { select: { id: true } } }, + }); + + return this.issueTokens(user); + } + + async refresh(refreshToken: string) { + const tokenHash = this.hashToken(refreshToken); + const stored = await this.prisma.refreshToken.findFirst({ + where: { tokenHash }, + include: { + user: { include: { ownedStore: { select: { id: 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 }, + include: { + ownedStore: { + select: { + id: true, + slug: true, + nameFa: true, + status: true, + }, + }, + }, + }); + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + email: user.email, + instagram: user.instagram, + about: user.about, + avatarUrl: user.avatarUrl, + role: user.role, + name: displayName(user), + store: user.ownedStore + ? { + id: user.ownedStore.id, + slug: user.ownedStore.slug, + nameFa: user.ownedStore.nameFa, + status: user.ownedStore.status, + } + : null, + }; + } + + /** Expose OTP helpers for store registration flow. */ + createAndSendOtpPublic(input: { + cellNumber: string; + purpose: SmsOtpPurpose; + payload?: unknown; + message: (code: string) => string; + }) { + return this.createAndSendOtp(input); + } + + assertValidOtpPublic(input: { + cellNumber: string; + purpose: SmsOtpPurpose; + code: string; + consume: boolean; + }) { + return this.assertValidOtp(input); + } + + issueTokensPublic(user: { + id: string; + cellNumber: string; + role: UserRole; + firstName: string; + lastName: string; + ownedStore?: { id: string } | null; + }) { + return this.issueTokens(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?: unknown; + 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), + }; + } + + 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: UserRole; + firstName: string; + lastName: string; + ownedStore?: { id: string } | null; + }) { + const storeId = + user.role === UserRole.STORE_ADMIN + ? (user.ownedStore?.id ?? null) + : null; + + const accessExpiresIn = this.config.get( + 'JWT_ACCESS_EXPIRES_IN', + '15m', + ); + const accessToken = await this.jwt.signAsync( + { sub: user.id, role: user.role, storeId }, + { + 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, + storeId, + 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..2d7f7ae --- /dev/null +++ b/src/auth/auth.types.ts @@ -0,0 +1,25 @@ +import { UserRole } from '@prisma/client'; + +export type AuthUser = { + id: string; + cellNumber: string; + role: UserRole; + firstName: string; + lastName: string; + storeId?: string | null; +}; + +export function isStoreAdmin(role: UserRole): boolean { + return role === UserRole.STORE_ADMIN; +} + +export function isSuperAdmin(role: UserRole): boolean { + return role === UserRole.SUPER_ADMIN; +} + +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..fd9943d --- /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) + @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..9577878 --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,11 @@ +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..45ba85e --- /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) + @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..cbe67bf --- /dev/null +++ b/src/auth/dto/register-verify.dto.ts @@ -0,0 +1,12 @@ +import { IsString, Length, Matches } from 'class-validator'; + +export class RegisterVerifyDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @Length(4, 8) + @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..f1085ae --- /dev/null +++ b/src/auth/strategies/jwt.strategy.ts @@ -0,0 +1,47 @@ +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; + storeId?: string | null; +}; + +@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 }, + include: { ownedStore: { select: { id: true } } }, + }); + + if (!user || user.disabled) { + throw new UnauthorizedException('نشست نامعتبر است'); + } + + return { + id: user.id, + cellNumber: user.cellNumber, + role: user.role, + firstName: user.firstName, + lastName: user.lastName, + storeId: user.ownedStore?.id ?? payload.storeId ?? null, + }; + } +} diff --git a/src/blogs/blogs.controller.ts b/src/blogs/blogs.controller.ts new file mode 100644 index 0000000..e8d3b79 --- /dev/null +++ b/src/blogs/blogs.controller.ts @@ -0,0 +1,58 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { CreateBlogDto, UpdateBlogDto } from './dto/blog.dto'; +import { BlogsService } from './blogs.service'; + +@Controller('blogs') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class BlogsController { + constructor(private readonly blogs: BlogsService) {} + + @Get() + list( + @CurrentUser() user: AuthUser, + @Query('storeId') storeId?: string, + ) { + return this.blogs.list(user, storeId); + } + + @Get(':id') + findOne(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.blogs.findOne(user, id); + } + + @Post() + create(@CurrentUser() user: AuthUser, @Body() dto: CreateBlogDto) { + return this.blogs.create(user, dto); + } + + @Patch(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateBlogDto, + ) { + return this.blogs.update(user, id, dto); + } + + @Delete(':id') + remove(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.blogs.remove(user, id); + } +} diff --git a/src/blogs/blogs.module.ts b/src/blogs/blogs.module.ts new file mode 100644 index 0000000..f1c682e --- /dev/null +++ b/src/blogs/blogs.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { BlogsController } from './blogs.controller'; +import { BlogsService } from './blogs.service'; + +@Module({ + controllers: [BlogsController], + providers: [BlogsService, StoreScopeService], + 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..b9fef7a --- /dev/null +++ b/src/blogs/blogs.service.ts @@ -0,0 +1,103 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { slugify } from '../common/slugify'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateBlogDto, UpdateBlogDto } from './dto/blog.dto'; + +@Injectable() +export class BlogsService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list(actor: AuthUser, storeIdQuery?: string) { + const storeId = + actor.role === UserRole.SUPER_ADMIN + ? storeIdQuery + : (await this.storeScope.requireActiveStore(actor)).id; + + return this.prisma.blogPost.findMany({ + where: storeId ? { storeId } : undefined, + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(actor: AuthUser, id: string) { + const post = await this.prisma.blogPost.findUnique({ where: { id } }); + if (!post) { + throw new NotFoundException('مطلب یافت نشد'); + } + await this.assertStoreAccess(actor, post.storeId); + return post; + } + + async create(actor: AuthUser, dto: CreateBlogDto) { + const store = await this.storeScope.requireActiveStore( + actor, + dto.storeId, + ); + + return this.prisma.blogPost.create({ + data: { + storeId: store.id, + title: dto.title, + summary: dto.summary ?? '', + body: dto.body ?? '', + author: dto.author ?? '', + imageUrl: dto.imageUrl, + imageKey: dto.imageKey, + published: dto.published ?? true, + metaTitle: dto.metaTitle, + metaDescription: dto.metaDescription, + slug: dto.slug ?? (slugify(dto.title) || undefined), + }, + }); + } + + async update(actor: AuthUser, id: string, dto: UpdateBlogDto) { + const existing = await this.prisma.blogPost.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('مطلب یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + + 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 } : {}), + ...(dto.metaTitle !== undefined ? { metaTitle: dto.metaTitle } : {}), + ...(dto.metaDescription !== undefined + ? { metaDescription: dto.metaDescription } + : {}), + ...(dto.slug !== undefined ? { slug: dto.slug } : {}), + }, + }); + } + + async remove(actor: AuthUser, id: string) { + const existing = await this.prisma.blogPost.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('مطلب یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + await this.prisma.blogPost.delete({ where: { id } }); + return { ok: true }; + } + + private async assertStoreAccess(actor: AuthUser, storeId: string) { + if (actor.role === UserRole.SUPER_ADMIN) return; + const store = await this.storeScope.requireActiveStore(actor); + if (store.id !== storeId) { + throw new NotFoundException('مطلب یافت نشد'); + } + } +} diff --git a/src/blogs/dto/blog.dto.ts b/src/blogs/dto/blog.dto.ts new file mode 100644 index 0000000..1fd743f --- /dev/null +++ b/src/blogs/dto/blog.dto.ts @@ -0,0 +1,95 @@ +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; + + @IsOptional() + @IsString() + metaTitle?: string; + + @IsOptional() + @IsString() + metaDescription?: string; + + @IsOptional() + @IsString() + slug?: string; + + @IsOptional() + @IsString() + storeId?: string; +} + +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; + + @IsOptional() + @IsString() + metaTitle?: string | null; + + @IsOptional() + @IsString() + metaDescription?: string | null; + + @IsOptional() + @IsString() + slug?: string | null; +} diff --git a/src/brands/brands.controller.ts b/src/brands/brands.controller.ts new file mode 100644 index 0000000..69e206e --- /dev/null +++ b/src/brands/brands.controller.ts @@ -0,0 +1,53 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { CreateBrandDto, UpdateBrandDto } from './dto/brand.dto'; +import { BrandsService } from './brands.service'; + +@Controller('brands') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class BrandsController { + constructor(private readonly brands: BrandsService) {} + + @Get() + list( + @CurrentUser() user: AuthUser, + @Query('storeId') storeId?: string, + ) { + return this.brands.list(user, storeId); + } + + @Post() + create(@CurrentUser() user: AuthUser, @Body() dto: CreateBrandDto) { + return this.brands.create(user, dto); + } + + @Patch(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateBrandDto, + ) { + return this.brands.update(user, id, dto); + } + + @Delete(':id') + remove(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.brands.remove(user, id); + } +} diff --git a/src/brands/brands.module.ts b/src/brands/brands.module.ts new file mode 100644 index 0000000..fa367c2 --- /dev/null +++ b/src/brands/brands.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { BrandsController } from './brands.controller'; +import { BrandsService } from './brands.service'; + +@Module({ + controllers: [BrandsController], + providers: [BrandsService, StoreScopeService], + exports: [BrandsService], +}) +export class BrandsModule {} diff --git a/src/brands/brands.service.ts b/src/brands/brands.service.ts new file mode 100644 index 0000000..1814b02 --- /dev/null +++ b/src/brands/brands.service.ts @@ -0,0 +1,79 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateBrandDto, UpdateBrandDto } from './dto/brand.dto'; + +@Injectable() +export class BrandsService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list(actor: AuthUser, storeIdQuery?: string) { + const storeId = + actor.role === UserRole.SUPER_ADMIN + ? storeIdQuery + : (await this.storeScope.requireActiveStore(actor)).id; + + return this.prisma.brand.findMany({ + where: storeId ? { storeId } : undefined, + orderBy: { nameFa: 'asc' }, + }); + } + + async create(actor: AuthUser, dto: CreateBrandDto) { + const store = await this.storeScope.requireActiveStore( + actor, + dto.storeId, + ); + + return this.prisma.brand.create({ + data: { + storeId: store.id, + nameFa: dto.nameFa, + nameEn: dto.nameEn ?? '', + imageUrl: dto.imageUrl, + imageKey: dto.imageKey, + }, + }); + } + + async update(actor: AuthUser, id: string, dto: UpdateBrandDto) { + const existing = await this.prisma.brand.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('برند یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + + return this.prisma.brand.update({ + where: { id }, + data: { + ...(dto.nameFa !== undefined ? { nameFa: dto.nameFa } : {}), + ...(dto.nameEn !== undefined ? { nameEn: dto.nameEn } : {}), + ...(dto.imageUrl !== undefined ? { imageUrl: dto.imageUrl } : {}), + ...(dto.imageKey !== undefined ? { imageKey: dto.imageKey } : {}), + }, + }); + } + + async remove(actor: AuthUser, id: string) { + const existing = await this.prisma.brand.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('برند یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + await this.prisma.brand.delete({ where: { id } }); + return { ok: true }; + } + + private async assertStoreAccess(actor: AuthUser, storeId: string) { + if (actor.role === UserRole.SUPER_ADMIN) return; + const store = await this.storeScope.requireActiveStore(actor); + if (store.id !== storeId) { + throw new NotFoundException('برند یافت نشد'); + } + } +} diff --git a/src/brands/dto/brand.dto.ts b/src/brands/dto/brand.dto.ts new file mode 100644 index 0000000..aa3ba1b --- /dev/null +++ b/src/brands/dto/brand.dto.ts @@ -0,0 +1,42 @@ +import { IsOptional, IsString, MinLength } from 'class-validator'; + +export class CreateBrandDto { + @IsString() + @MinLength(1) + nameFa!: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsString() + imageKey?: string; + + @IsOptional() + @IsString() + storeId?: string; +} + +export class UpdateBrandDto { + @IsOptional() + @IsString() + @MinLength(1) + nameFa?: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + imageUrl?: string | null; + + @IsOptional() + @IsString() + imageKey?: string | null; +} diff --git a/src/catalog/catalog.controller.ts b/src/catalog/catalog.controller.ts new file mode 100644 index 0000000..d776528 --- /dev/null +++ b/src/catalog/catalog.controller.ts @@ -0,0 +1,60 @@ +import { Controller, Get } from '@nestjs/common'; + +export const colorCatalog = [ + { id: 'black', nameFa: 'مشکی', hex: '#0a0a0a' }, + { id: 'white', nameFa: 'سفید', hex: '#f5f5f5' }, + { id: 'cream', nameFa: 'کرم', hex: '#e8dcc8' }, + { id: 'beige', nameFa: 'بژ', hex: '#c4a882' }, + { id: 'navy', nameFa: 'سرمه‌ای', hex: '#1b2a4a' }, + { id: 'olive', nameFa: 'زیتونی', hex: '#556b2f' }, + { id: 'burgundy', nameFa: 'زرشکی', hex: '#6b1e2a' }, + { id: 'gray', nameFa: 'طوسی', hex: '#8a8a8a' }, + { id: 'brown', nameFa: 'قهوه‌ای', hex: '#6b4423' }, + { id: 'red', nameFa: 'قرمز', hex: '#b91c1c' }, +]; + +export const sizeCatalog = [ + { id: 'xs', nameFa: 'XS' }, + { id: 's', nameFa: 'S' }, + { id: 'm', nameFa: 'M' }, + { id: 'l', nameFa: 'L' }, + { id: 'xl', nameFa: 'XL' }, + { id: 'xxl', nameFa: 'XXL' }, + { id: '36', nameFa: '36' }, + { id: '38', nameFa: '38' }, + { id: '40', nameFa: '40' }, + { id: '42', nameFa: '42' }, + { id: '44', nameFa: '44' }, +]; + +export const packageCatalog = [ + { id: '6', nameFa: '۶ تایی' }, + { id: '12', nameFa: '۱۲ تایی' }, +]; + +@Controller('catalog') +export class CatalogController { + @Get() + getAll() { + return { + colors: colorCatalog, + sizes: sizeCatalog, + packages: packageCatalog, + }; + } + + @Get('colors') + colors() { + return colorCatalog; + } + + @Get('sizes') + sizes() { + return sizeCatalog; + } + + @Get('packages') + packages() { + return packageCatalog; + } +} diff --git a/src/catalog/catalog.module.ts b/src/catalog/catalog.module.ts new file mode 100644 index 0000000..ccfd401 --- /dev/null +++ b/src/catalog/catalog.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { CatalogController } from './catalog.controller'; + +@Module({ + controllers: [CatalogController], +}) +export class CatalogModule {} diff --git a/src/categories/categories.controller.ts b/src/categories/categories.controller.ts new file mode 100644 index 0000000..688f6d6 --- /dev/null +++ b/src/categories/categories.controller.ts @@ -0,0 +1,53 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { CreateCategoryDto, UpdateCategoryDto } from './dto/category.dto'; +import { CategoriesService } from './categories.service'; + +@Controller('categories') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class CategoriesController { + constructor(private readonly categories: CategoriesService) {} + + @Get() + list( + @CurrentUser() user: AuthUser, + @Query('storeId') storeId?: string, + ) { + return this.categories.list(user, storeId); + } + + @Post() + create(@CurrentUser() user: AuthUser, @Body() dto: CreateCategoryDto) { + return this.categories.create(user, dto); + } + + @Patch(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateCategoryDto, + ) { + return this.categories.update(user, id, dto); + } + + @Delete(':id') + remove(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.categories.remove(user, id); + } +} diff --git a/src/categories/categories.module.ts b/src/categories/categories.module.ts new file mode 100644 index 0000000..6773e2e --- /dev/null +++ b/src/categories/categories.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { CategoriesController } from './categories.controller'; +import { CategoriesService } from './categories.service'; + +@Module({ + controllers: [CategoriesController], + providers: [CategoriesService, StoreScopeService], + exports: [CategoriesService], +}) +export class CategoriesModule {} diff --git a/src/categories/categories.service.ts b/src/categories/categories.service.ts new file mode 100644 index 0000000..4884905 --- /dev/null +++ b/src/categories/categories.service.ts @@ -0,0 +1,107 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateCategoryDto, UpdateCategoryDto } from './dto/category.dto'; + +@Injectable() +export class CategoriesService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list(actor: AuthUser, storeIdQuery?: string) { + const storeId = + actor.role === UserRole.SUPER_ADMIN + ? storeIdQuery + : (await this.storeScope.requireActiveStore(actor)).id; + + const categories = await this.prisma.category.findMany({ + where: storeId ? { storeId } : undefined, + orderBy: [{ sortOrder: 'asc' }, { nameFa: 'asc' }], + }); + + return this.buildTree(categories); + } + + async create(actor: AuthUser, dto: CreateCategoryDto) { + const store = await this.storeScope.requireActiveStore( + actor, + dto.storeId, + ); + + return this.prisma.category.create({ + data: { + storeId: store.id, + nameFa: dto.nameFa, + nameEn: dto.nameEn ?? '', + parentId: dto.parentId, + sortOrder: dto.sortOrder ?? 0, + colors: dto.colors ?? [], + sizes: dto.sizes ?? [], + packages: dto.packages ?? [], + }, + }); + } + + async update(actor: AuthUser, id: string, dto: UpdateCategoryDto) { + const existing = await this.prisma.category.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('دسته‌بندی یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + + return this.prisma.category.update({ + where: { id }, + data: { + ...(dto.nameFa !== undefined ? { nameFa: dto.nameFa } : {}), + ...(dto.nameEn !== undefined ? { nameEn: dto.nameEn } : {}), + ...(dto.parentId !== undefined ? { parentId: dto.parentId } : {}), + ...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}), + ...(dto.colors !== undefined ? { colors: dto.colors } : {}), + ...(dto.sizes !== undefined ? { sizes: dto.sizes } : {}), + ...(dto.packages !== undefined ? { packages: dto.packages } : {}), + }, + }); + } + + async remove(actor: AuthUser, id: string) { + const existing = await this.prisma.category.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('دسته‌بندی یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + await this.prisma.category.delete({ where: { id } }); + return { ok: true }; + } + + private buildTree< + T extends { id: string; parentId: string | null }, + >(items: T[]) { + const byParent = new Map(); + for (const item of items) { + const key = item.parentId; + const list = byParent.get(key) ?? []; + list.push(item); + byParent.set(key, list); + } + + const walk = (parentId: string | null): Array => + (byParent.get(parentId) ?? []).map((node) => ({ + ...node, + children: walk(node.id), + })); + + return walk(null); + } + + private async assertStoreAccess(actor: AuthUser, storeId: string) { + if (actor.role === UserRole.SUPER_ADMIN) return; + const store = await this.storeScope.requireActiveStore(actor); + if (store.id !== storeId) { + throw new NotFoundException('دسته‌بندی یافت نشد'); + } + } +} diff --git a/src/categories/dto/category.dto.ts b/src/categories/dto/category.dto.ts new file mode 100644 index 0000000..6fa0623 --- /dev/null +++ b/src/categories/dto/category.dto.ts @@ -0,0 +1,81 @@ +import { + IsArray, + IsInt, + IsOptional, + IsString, + Min, + MinLength, +} from 'class-validator'; + +export class CreateCategoryDto { + @IsString() + @MinLength(1) + nameFa!: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + parentId?: string; + + @IsOptional() + @IsInt() + @Min(0) + sortOrder?: number; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + colors?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + sizes?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + packages?: string[]; + + @IsOptional() + @IsString() + storeId?: string; +} + +export class UpdateCategoryDto { + @IsOptional() + @IsString() + @MinLength(1) + nameFa?: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + parentId?: string | null; + + @IsOptional() + @IsInt() + @Min(0) + sortOrder?: number; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + colors?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + sizes?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + packages?: string[]; +} diff --git a/src/common/slugify.ts b/src/common/slugify.ts new file mode 100644 index 0000000..c5e884b --- /dev/null +++ b/src/common/slugify.ts @@ -0,0 +1,9 @@ +export function slugify(input: string): string { + return input + .trim() + .toLowerCase() + .replace(/[\s_]+/g, '-') + .replace(/[^a-z0-9-]/g, '') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); +} diff --git a/src/common/store-scope.service.ts b/src/common/store-scope.service.ts new file mode 100644 index 0000000..b6717cf --- /dev/null +++ b/src/common/store-scope.service.ts @@ -0,0 +1,76 @@ +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { Store, StoreStatus, UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class StoreScopeService { + constructor(private readonly prisma: PrismaService) {} + + /** + * STORE_ADMIN must own an active store for product/catalog CRUD. + * SUPER_ADMIN may pass an explicit storeId. + */ + async requireActiveStore( + actor: AuthUser, + storeId?: string, + ): Promise { + if (actor.role === UserRole.SUPER_ADMIN) { + if (!storeId) { + throw new ForbiddenException('شناسه فروشگاه الزامی است'); + } + const store = await this.prisma.store.findUnique({ + where: { id: storeId }, + }); + if (!store) { + throw new NotFoundException('فروشگاه یافت نشد'); + } + return store; + } + + if (actor.role !== UserRole.STORE_ADMIN) { + throw new ForbiddenException('دسترسی مجاز نیست'); + } + + const store = await this.prisma.store.findUnique({ + where: { ownerId: actor.id }, + }); + if (!store) { + throw new ForbiddenException('فروشگاهی به این حساب متصل نیست'); + } + if (store.status !== StoreStatus.active) { + throw new ForbiddenException( + 'فروشگاه هنوز فعال نشده است. فقط پروفایل فروشگاه قابل ویرایش است', + ); + } + return store; + } + + /** Own store for profile read/update — any status except missing. */ + async requireOwnedStore(actor: AuthUser): Promise { + if (actor.role !== UserRole.STORE_ADMIN) { + throw new ForbiddenException('دسترسی مجاز نیست'); + } + const store = await this.prisma.store.findUnique({ + where: { ownerId: actor.id }, + }); + if (!store) { + throw new NotFoundException('فروشگاه یافت نشد'); + } + return store; + } + + resolveStoreIdFilter( + actor: AuthUser, + queryStoreId?: string, + ): string | undefined { + if (actor.role === UserRole.SUPER_ADMIN) { + return queryStoreId; + } + return actor.storeId ?? undefined; + } +} diff --git a/src/customers/customers.controller.ts b/src/customers/customers.controller.ts new file mode 100644 index 0000000..44146d9 --- /dev/null +++ b/src/customers/customers.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { CustomersService } from './customers.service'; + +@Controller('customers') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class CustomersController { + constructor(private readonly customers: CustomersService) {} + + @Get() + list(@CurrentUser() user: AuthUser) { + return this.customers.list(user); + } +} diff --git a/src/customers/customers.module.ts b/src/customers/customers.module.ts new file mode 100644 index 0000000..6adde68 --- /dev/null +++ b/src/customers/customers.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { CustomersController } from './customers.controller'; +import { CustomersService } from './customers.service'; + +@Module({ + controllers: [CustomersController], + providers: [CustomersService, StoreScopeService], +}) +export class CustomersModule {} diff --git a/src/customers/customers.service.ts b/src/customers/customers.service.ts new file mode 100644 index 0000000..8d83214 --- /dev/null +++ b/src/customers/customers.service.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class CustomersService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list(actor: AuthUser) { + if (actor.role === UserRole.SUPER_ADMIN) { + return this.prisma.user.findMany({ + where: { role: UserRole.CUSTOMER }, + select: this.customerSelect(), + orderBy: { createdAt: 'desc' }, + }); + } + + const store = await this.storeScope.requireActiveStore(actor); + const items = await this.prisma.orderItem.findMany({ + where: { storeId: store.id, order: { customerId: { not: null } } }, + select: { + order: { + select: { + customer: { + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + email: true, + instagram: true, + about: true, + avatarUrl: true, + disabled: true, + createdAt: true, + }, + }, + }, + }, + }, + }); + + const byId = new Map< + string, + { + id: string; + firstName: string; + lastName: string; + cellNumber: string; + email: string | null; + instagram: string | null; + about: string | null; + avatarUrl: string | null; + disabled: boolean; + createdAt: Date; + name: string; + } + >(); + + for (const item of items) { + const customer = item.order.customer; + if (customer && !byId.has(customer.id)) { + byId.set(customer.id, { + ...customer, + name: `${customer.firstName} ${customer.lastName}`.trim(), + }); + } + } + return [...byId.values()]; + } + + private customerSelect() { + return { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + email: true, + instagram: true, + about: true, + avatarUrl: true, + disabled: true, + createdAt: true, + } as const; + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..85261c9 --- /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 ?? 3002); + await app.listen(port); + // eslint-disable-next-line no-console + console.log(`Mana Collection 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..a43a12c --- /dev/null +++ b/src/media/dto/media-upload-query.dto.ts @@ -0,0 +1,17 @@ +import { IsIn } from 'class-validator'; + +export const MEDIA_KINDS = [ + 'main', + 'gallery', + 'blog', + 'brand', + 'store', + 'avatar', + '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..e11af4b --- /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.STORE_ADMIN, UserRole.SUPER_ADMIN) +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..815109e --- /dev/null +++ b/src/media/media.service.ts @@ -0,0 +1,94 @@ +import { + BadRequestException, + Injectable, + PayloadTooLargeException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { randomUUID } from 'crypto'; +import { extname } from 'path'; +import { + blogMediaKey, + brandMediaKey, + productGalleryKey, + productMainKey, + storeMediaKey, + tempMediaKey, + userMediaKey, +} 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 'brand': + return brandMediaKey(fileName); + case 'store': + return storeMediaKey(fileName); + case 'avatar': + return userMediaKey(fileName); + case 'temp': + return tempMediaKey(fileName); + default: + throw new BadRequestException('نوع فایل نامعتبر است'); + } + } +} diff --git a/src/orders/dto/order.dto.ts b/src/orders/dto/order.dto.ts new file mode 100644 index 0000000..8c4fb7b --- /dev/null +++ b/src/orders/dto/order.dto.ts @@ -0,0 +1,43 @@ +import { IsIn, IsOptional, IsString } from 'class-validator'; + +export class UpdateOrderStoreStatusDto { + @IsString() + @IsIn([ + 'pending', + 'confirmed', + 'preparing', + 'shipped', + 'delivered', + 'cancelled', + ]) + status!: + | 'pending' + | 'confirmed' + | 'preparing' + | 'shipped' + | 'delivered' + | 'cancelled'; +} + +export class UpdateOrderStatusDto { + @IsString() + @IsIn([ + 'pending', + 'confirmed', + 'preparing', + 'shipped', + 'delivered', + 'cancelled', + ]) + status!: + | 'pending' + | 'confirmed' + | 'preparing' + | 'shipped' + | 'delivered' + | 'cancelled'; + + @IsOptional() + @IsString() + note?: string; +} diff --git a/src/orders/orders.controller.ts b/src/orders/orders.controller.ts new file mode 100644 index 0000000..6f2e8ca --- /dev/null +++ b/src/orders/orders.controller.ts @@ -0,0 +1,56 @@ +import { + Body, + Controller, + Get, + Param, + Patch, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { + UpdateOrderStatusDto, + UpdateOrderStoreStatusDto, +} from './dto/order.dto'; +import { OrdersService } from './orders.service'; + +@Controller('orders') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class OrdersController { + constructor(private readonly orders: OrdersService) {} + + @Get() + list(@CurrentUser() user: AuthUser) { + return this.orders.list(user); + } + + @Get(':id') + findOne(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.orders.findOne(user, id); + } + + @Patch(':id/fulfillment') + @Roles(UserRole.STORE_ADMIN) + updateFulfillment( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateOrderStoreStatusDto, + ) { + return this.orders.updateStoreFulfillment(user, id, dto); + } + + @Patch(':id/status') + @Roles(UserRole.SUPER_ADMIN) + updateStatus( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateOrderStatusDto, + ) { + return this.orders.updateStatus(user, id, dto); + } +} diff --git a/src/orders/orders.module.ts b/src/orders/orders.module.ts new file mode 100644 index 0000000..0fca3fe --- /dev/null +++ b/src/orders/orders.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { OrdersController } from './orders.controller'; +import { OrdersService } from './orders.service'; + +@Module({ + controllers: [OrdersController], + providers: [OrdersService, StoreScopeService], +}) +export class OrdersModule {} diff --git a/src/orders/orders.service.ts b/src/orders/orders.service.ts new file mode 100644 index 0000000..52468b2 --- /dev/null +++ b/src/orders/orders.service.ts @@ -0,0 +1,130 @@ +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { OrderStoreStatus, OrderStatus, UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { + UpdateOrderStatusDto, + UpdateOrderStoreStatusDto, +} from './dto/order.dto'; + +@Injectable() +export class OrdersService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list(actor: AuthUser) { + if (actor.role === UserRole.SUPER_ADMIN) { + return this.prisma.order.findMany({ + include: { + items: true, + fulfillments: true, + customer: { + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + const store = await this.storeScope.requireActiveStore(actor); + return this.prisma.order.findMany({ + where: { + items: { some: { storeId: store.id } }, + }, + include: { + items: { where: { storeId: store.id } }, + fulfillments: { where: { storeId: store.id } }, + customer: { + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(actor: AuthUser, id: string) { + if (actor.role === UserRole.SUPER_ADMIN) { + const order = await this.prisma.order.findUnique({ + where: { id }, + include: { items: true, fulfillments: true, customer: true }, + }); + if (!order) throw new NotFoundException('سفارش یافت نشد'); + return order; + } + + const store = await this.storeScope.requireActiveStore(actor); + const order = await this.prisma.order.findFirst({ + where: { + id, + items: { some: { storeId: store.id } }, + }, + include: { + items: { where: { storeId: store.id } }, + fulfillments: { where: { storeId: store.id } }, + customer: true, + }, + }); + if (!order) throw new NotFoundException('سفارش یافت نشد'); + return order; + } + + async updateStoreFulfillment( + actor: AuthUser, + orderId: string, + dto: UpdateOrderStoreStatusDto, + ) { + const store = await this.storeScope.requireActiveStore(actor); + const fulfillment = await this.prisma.orderStoreFulfillment.findUnique({ + where: { + orderId_storeId: { orderId, storeId: store.id }, + }, + }); + if (!fulfillment) { + throw new NotFoundException('وضعیت سفارش فروشگاه یافت نشد'); + } + + return this.prisma.orderStoreFulfillment.update({ + where: { id: fulfillment.id }, + data: { status: dto.status as OrderStoreStatus }, + }); + } + + async updateStatus( + actor: AuthUser, + orderId: string, + dto: UpdateOrderStatusDto, + ) { + if (actor.role !== UserRole.SUPER_ADMIN) { + throw new ForbiddenException('فقط سوپر ادمین می‌تواند وضعیت کلی را تغییر دهد'); + } + + const order = await this.prisma.order.findUnique({ where: { id: orderId } }); + if (!order) throw new NotFoundException('سفارش یافت نشد'); + + return this.prisma.order.update({ + where: { id: orderId }, + data: { + status: dto.status as OrderStatus, + ...(dto.note !== undefined ? { note: dto.note } : {}), + }, + include: { items: true, fulfillments: 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..ba00c9f --- /dev/null +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,16 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } 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..0785f5f --- /dev/null +++ b/src/products/dto/create-product.dto.ts @@ -0,0 +1,181 @@ +import { Type } from 'class-transformer'; +import { + IsArray, + IsBoolean, + IsInt, + IsOptional, + IsString, + Min, + MinLength, + ValidateNested, +} from 'class-validator'; + +export class ProductVariantDto { + @IsOptional() + @IsString() + id?: string; + + @IsOptional() + @IsString() + colorId?: string; + + @IsOptional() + @IsString() + sizeId?: string; + + @IsOptional() + @IsString() + packageId?: string; + + @IsInt() + @Min(0) + price!: number; + + @IsOptional() + @IsInt() + @Min(0) + discountPrice?: number; +} + +export class CreateProductDto { + @IsString() + @MinLength(1) + nameFa!: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + summary?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsInt() + @Min(0) + price!: number; + + @IsOptional() + @IsInt() + @Min(0) + discountPrice?: number; + + @IsOptional() + @IsString() + categoryId?: string; + + @IsOptional() + @IsString() + brandId?: string; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsString() + imageKey?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + galleryUrls?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + galleryKeys?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; + + @IsOptional() + @IsBoolean() + published?: boolean; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ProductVariantDto) + variants?: ProductVariantDto[]; + + /** SUPER_ADMIN only */ + @IsOptional() + @IsString() + storeId?: string; +} + +export class UpdateProductDto { + @IsOptional() + @IsString() + @MinLength(1) + nameFa?: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + summary?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsInt() + @Min(0) + price?: number; + + @IsOptional() + @IsInt() + @Min(0) + discountPrice?: number | null; + + @IsOptional() + @IsString() + categoryId?: string | null; + + @IsOptional() + @IsString() + brandId?: string | null; + + @IsOptional() + @IsString() + imageUrl?: string | null; + + @IsOptional() + @IsString() + imageKey?: string | null; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + galleryUrls?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + galleryKeys?: string[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; + + @IsOptional() + @IsBoolean() + published?: boolean; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ProductVariantDto) + variants?: ProductVariantDto[]; +} diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts new file mode 100644 index 0000000..4808b74 --- /dev/null +++ b/src/products/products.controller.ts @@ -0,0 +1,71 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { + CreateProductDto, + UpdateProductDto, +} from './dto/create-product.dto'; +import { ProductsService } from './products.service'; + +@Controller('products') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles(UserRole.STORE_ADMIN, UserRole.SUPER_ADMIN) +export class ProductsController { + constructor(private readonly products: ProductsService) {} + + @Get() + list( + @CurrentUser() user: AuthUser, + @Query('storeId') storeId?: string, + @Query('name') name?: string, + @Query('categoryId') categoryId?: string, + @Query('minPrice') minPrice?: number, + @Query('maxPrice') maxPrice?: number, + ) { + return this.products.list(user, { + storeId, + name, + categoryId, + minPrice, + maxPrice, + }); + } + + @Get(':id') + findOne(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.products.findOne(user, id); + } + + @Post() + create(@CurrentUser() user: AuthUser, @Body() dto: CreateProductDto) { + return this.products.create(user, dto); + } + + @Patch(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateProductDto, + ) { + return this.products.update(user, id, dto); + } + + @Delete(':id') + remove(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.products.remove(user, id); + } +} diff --git a/src/products/products.module.ts b/src/products/products.module.ts new file mode 100644 index 0000000..0d2615d --- /dev/null +++ b/src/products/products.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { StoreScopeService } from '../common/store-scope.service'; +import { ProductsController } from './products.controller'; +import { ProductsService } from './products.service'; + +@Module({ + controllers: [ProductsController], + providers: [ProductsService, StoreScopeService], + 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..4b2bb3f --- /dev/null +++ b/src/products/products.service.ts @@ -0,0 +1,190 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Prisma, UserRole } from '@prisma/client'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateProductDto, + UpdateProductDto, +} from './dto/create-product.dto'; + +@Injectable() +export class ProductsService { + constructor( + private readonly prisma: PrismaService, + private readonly storeScope: StoreScopeService, + ) {} + + async list( + actor: AuthUser, + query: { + storeId?: string; + name?: string; + categoryId?: string; + minPrice?: number; + maxPrice?: number; + }, + ) { + const storeId = + actor.role === UserRole.SUPER_ADMIN + ? query.storeId + : (await this.storeScope.requireActiveStore(actor)).id; + + const where: Prisma.ProductWhereInput = { + ...(storeId ? { storeId } : {}), + ...(query.categoryId ? { categoryId: query.categoryId } : {}), + ...(query.name + ? { + OR: [ + { nameFa: { contains: query.name, mode: 'insensitive' } }, + { nameEn: { contains: query.name, mode: 'insensitive' } }, + ], + } + : {}), + ...(query.minPrice !== undefined || query.maxPrice !== undefined + ? { + price: { + ...(query.minPrice !== undefined ? { gte: query.minPrice } : {}), + ...(query.maxPrice !== undefined ? { lte: query.maxPrice } : {}), + }, + } + : {}), + }; + + return this.prisma.product.findMany({ + where, + include: { + variants: true, + category: { select: { id: true, nameFa: true } }, + brand: { select: { id: true, nameFa: true } }, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(actor: AuthUser, id: string) { + const product = await this.prisma.product.findUnique({ + where: { id }, + include: { variants: true, category: true, brand: true }, + }); + if (!product) { + throw new NotFoundException('محصول یافت نشد'); + } + await this.assertStoreAccess(actor, product.storeId); + return product; + } + + async create(actor: AuthUser, dto: CreateProductDto) { + const store = await this.storeScope.requireActiveStore( + actor, + dto.storeId, + ); + + return this.prisma.product.create({ + data: { + storeId: store.id, + nameFa: dto.nameFa, + nameEn: dto.nameEn ?? '', + summary: dto.summary ?? '', + description: dto.description ?? '', + price: dto.price, + discountPrice: dto.discountPrice, + categoryId: dto.categoryId, + brandId: dto.brandId, + imageUrl: dto.imageUrl, + imageKey: dto.imageKey, + galleryUrls: dto.galleryUrls ?? [], + galleryKeys: dto.galleryKeys ?? [], + tags: dto.tags ?? [], + published: dto.published ?? true, + variants: dto.variants?.length + ? { + create: dto.variants.map((v) => ({ + colorId: v.colorId ?? '', + sizeId: v.sizeId ?? '', + packageId: v.packageId ?? '', + price: v.price, + discountPrice: v.discountPrice, + })), + } + : undefined, + }, + include: { variants: true }, + }); + } + + async update(actor: AuthUser, id: string, dto: UpdateProductDto) { + const existing = await this.prisma.product.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('محصول یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + + return this.prisma.$transaction(async (tx) => { + if (dto.variants) { + await tx.productVariant.deleteMany({ where: { productId: id } }); + if (dto.variants.length) { + await tx.productVariant.createMany({ + data: dto.variants.map((v) => ({ + productId: id, + colorId: v.colorId ?? '', + sizeId: v.sizeId ?? '', + packageId: v.packageId ?? '', + price: v.price, + discountPrice: v.discountPrice, + })), + }); + } + } + + return tx.product.update({ + where: { id }, + data: { + ...(dto.nameFa !== undefined ? { nameFa: dto.nameFa } : {}), + ...(dto.nameEn !== undefined ? { nameEn: dto.nameEn } : {}), + ...(dto.summary !== undefined ? { summary: dto.summary } : {}), + ...(dto.description !== undefined + ? { description: dto.description } + : {}), + ...(dto.price !== undefined ? { price: dto.price } : {}), + ...(dto.discountPrice !== undefined + ? { discountPrice: dto.discountPrice } + : {}), + ...(dto.categoryId !== undefined + ? { categoryId: dto.categoryId } + : {}), + ...(dto.brandId !== undefined ? { brandId: dto.brandId } : {}), + ...(dto.imageUrl !== undefined ? { imageUrl: dto.imageUrl } : {}), + ...(dto.imageKey !== undefined ? { imageKey: dto.imageKey } : {}), + ...(dto.galleryUrls !== undefined + ? { galleryUrls: dto.galleryUrls } + : {}), + ...(dto.galleryKeys !== undefined + ? { galleryKeys: dto.galleryKeys } + : {}), + ...(dto.tags !== undefined ? { tags: dto.tags } : {}), + ...(dto.published !== undefined ? { published: dto.published } : {}), + }, + include: { variants: true }, + }); + }); + } + + async remove(actor: AuthUser, id: string) { + const existing = await this.prisma.product.findUnique({ where: { id } }); + if (!existing) { + throw new NotFoundException('محصول یافت نشد'); + } + await this.assertStoreAccess(actor, existing.storeId); + await this.prisma.product.delete({ where: { id } }); + return { ok: true }; + } + + private async assertStoreAccess(actor: AuthUser, storeId: string) { + if (actor.role === UserRole.SUPER_ADMIN) return; + const store = await this.storeScope.requireActiveStore(actor); + if (store.id !== storeId) { + throw new NotFoundException('محصول یافت نشد'); + } + } +} diff --git a/src/profile/dto/profile.dto.ts b/src/profile/dto/profile.dto.ts new file mode 100644 index 0000000..8f6ab9c --- /dev/null +++ b/src/profile/dto/profile.dto.ts @@ -0,0 +1,80 @@ +import { IsOptional, IsString, MinLength } from 'class-validator'; + +export class UpdateProfileDto { + @IsOptional() + @IsString() + @MinLength(1) + firstName?: string; + + @IsOptional() + @IsString() + @MinLength(1) + lastName?: string; + + @IsOptional() + @IsString() + email?: string | null; + + @IsOptional() + @IsString() + instagram?: string | null; + + @IsOptional() + @IsString() + about?: string | null; + + @IsOptional() + @IsString() + avatarUrl?: string | null; + + @IsOptional() + @IsString() + avatarKey?: string | null; +} + +export class CreateAddressDto { + @IsOptional() + @IsString() + label?: string; + + @IsString() + @MinLength(1) + province!: string; + + @IsString() + @MinLength(1) + city!: string; + + @IsString() + @MinLength(1) + fullAddress!: string; + + @IsOptional() + @IsString() + landline?: string; +} + +export class UpdateAddressDto { + @IsOptional() + @IsString() + label?: string; + + @IsOptional() + @IsString() + @MinLength(1) + province?: string; + + @IsOptional() + @IsString() + @MinLength(1) + city?: string; + + @IsOptional() + @IsString() + @MinLength(1) + fullAddress?: string; + + @IsOptional() + @IsString() + landline?: string | null; +} diff --git a/src/profile/profile.controller.ts b/src/profile/profile.controller.ts new file mode 100644 index 0000000..a6fe39c --- /dev/null +++ b/src/profile/profile.controller.ts @@ -0,0 +1,62 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + UseGuards, +} from '@nestjs/common'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { + CreateAddressDto, + UpdateAddressDto, + UpdateProfileDto, +} from './dto/profile.dto'; +import { ProfileService } from './profile.service'; + +@Controller('profile') +@UseGuards(JwtAuthGuard) +export class ProfileController { + constructor(private readonly profile: ProfileService) {} + + @Get() + get(@CurrentUser() user: AuthUser) { + return this.profile.getProfile(user); + } + + @Patch() + update(@CurrentUser() user: AuthUser, @Body() dto: UpdateProfileDto) { + return this.profile.updateProfile(user, dto); + } + + @Get('addresses') + listAddresses(@CurrentUser() user: AuthUser) { + return this.profile.listAddresses(user); + } + + @Post('addresses') + createAddress( + @CurrentUser() user: AuthUser, + @Body() dto: CreateAddressDto, + ) { + return this.profile.createAddress(user, dto); + } + + @Patch('addresses/:id') + updateAddress( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @Body() dto: UpdateAddressDto, + ) { + return this.profile.updateAddress(user, id, dto); + } + + @Delete('addresses/:id') + removeAddress(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.profile.removeAddress(user, id); + } +} diff --git a/src/profile/profile.module.ts b/src/profile/profile.module.ts new file mode 100644 index 0000000..7a66185 --- /dev/null +++ b/src/profile/profile.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ProfileController } from './profile.controller'; +import { ProfileService } from './profile.service'; + +@Module({ + controllers: [ProfileController], + providers: [ProfileService], +}) +export class ProfileModule {} diff --git a/src/profile/profile.service.ts b/src/profile/profile.service.ts new file mode 100644 index 0000000..7fb004b --- /dev/null +++ b/src/profile/profile.service.ts @@ -0,0 +1,123 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AuthUser, displayName } from '../auth/auth.types'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateAddressDto, + UpdateAddressDto, + UpdateProfileDto, +} from './dto/profile.dto'; + +@Injectable() +export class ProfileService { + constructor(private readonly prisma: PrismaService) {} + + async getProfile(actor: AuthUser) { + const user = await this.prisma.user.findUniqueOrThrow({ + where: { id: actor.id }, + include: { addresses: true }, + }); + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + email: user.email, + instagram: user.instagram, + about: user.about, + avatarUrl: user.avatarUrl, + avatarKey: user.avatarKey, + role: user.role, + name: displayName(user), + addresses: user.addresses, + }; + } + + async updateProfile(actor: AuthUser, dto: UpdateProfileDto) { + const user = await this.prisma.user.update({ + where: { id: actor.id }, + data: { + ...(dto.firstName !== undefined + ? { firstName: dto.firstName.trim() } + : {}), + ...(dto.lastName !== undefined + ? { lastName: dto.lastName.trim() } + : {}), + ...(dto.email !== undefined ? { email: dto.email } : {}), + ...(dto.instagram !== undefined ? { instagram: dto.instagram } : {}), + ...(dto.about !== undefined ? { about: dto.about } : {}), + ...(dto.avatarUrl !== undefined ? { avatarUrl: dto.avatarUrl } : {}), + ...(dto.avatarKey !== undefined ? { avatarKey: dto.avatarKey } : {}), + }, + }); + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + cellNumber: user.cellNumber, + email: user.email, + instagram: user.instagram, + about: user.about, + avatarUrl: user.avatarUrl, + role: user.role, + name: displayName(user), + }; + } + + listAddresses(actor: AuthUser) { + return this.prisma.userAddress.findMany({ + where: { userId: actor.id }, + orderBy: { label: 'asc' }, + }); + } + + createAddress(actor: AuthUser, dto: CreateAddressDto) { + return this.prisma.userAddress.create({ + data: { + userId: actor.id, + label: dto.label ?? '', + province: dto.province, + city: dto.city, + fullAddress: dto.fullAddress, + landline: dto.landline, + }, + }); + } + + async updateAddress( + actor: AuthUser, + id: string, + dto: UpdateAddressDto, + ) { + await this.requireOwnedAddress(actor.id, id); + return this.prisma.userAddress.update({ + where: { id }, + data: { + ...(dto.label !== undefined ? { label: dto.label } : {}), + ...(dto.province !== undefined ? { province: dto.province } : {}), + ...(dto.city !== undefined ? { city: dto.city } : {}), + ...(dto.fullAddress !== undefined + ? { fullAddress: dto.fullAddress } + : {}), + ...(dto.landline !== undefined ? { landline: dto.landline } : {}), + }, + }); + } + + async removeAddress(actor: AuthUser, id: string) { + await this.requireOwnedAddress(actor.id, id); + await this.prisma.userAddress.delete({ where: { id } }); + return { ok: true }; + } + + private async requireOwnedAddress(userId: string, id: string) { + const address = await this.prisma.userAddress.findFirst({ + where: { id, userId }, + }); + if (!address) { + throw new NotFoundException('آدرس یافت نشد'); + } + return address; + } +} diff --git a/src/public/public.controller.ts b/src/public/public.controller.ts new file mode 100644 index 0000000..f1a2943 --- /dev/null +++ b/src/public/public.controller.ts @@ -0,0 +1,41 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { PublicService } from './public.service'; + +@Controller('public') +export class PublicController { + constructor(private readonly publicService: PublicService) {} + + @Get('stores') + listStores() { + return this.publicService.listStores(); + } + + @Get('products') + listProducts( + @Query('storeId') storeId?: string, + @Query('storeSlug') storeSlug?: string, + @Query('name') name?: string, + @Query('categoryId') categoryId?: string, + @Query('minPrice') minPrice?: number, + @Query('maxPrice') maxPrice?: number, + ) { + return this.publicService.listProducts({ + storeId, + storeSlug, + name, + categoryId, + minPrice, + maxPrice, + }); + } + + @Get('products/:id') + findProduct(@Param('id') id: string) { + return this.publicService.findProduct(id); + } + + @Get('blogs') + listBlogs(@Query('storeSlug') storeSlug?: string) { + return this.publicService.listBlogs(storeSlug); + } +} diff --git a/src/public/public.module.ts b/src/public/public.module.ts new file mode 100644 index 0000000..e0fbe03 --- /dev/null +++ b/src/public/public.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PublicController } from './public.controller'; +import { PublicService } from './public.service'; + +@Module({ + controllers: [PublicController], + providers: [PublicService], +}) +export class PublicModule {} diff --git a/src/public/public.service.ts b/src/public/public.service.ts new file mode 100644 index 0000000..6533f75 --- /dev/null +++ b/src/public/public.service.ts @@ -0,0 +1,113 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Prisma, StoreStatus } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class PublicService { + constructor(private readonly prisma: PrismaService) {} + + listStores() { + return this.prisma.store.findMany({ + where: { status: StoreStatus.active }, + select: { + id: true, + slug: true, + nameFa: true, + nameEn: true, + description: true, + logoUrl: true, + }, + orderBy: { nameFa: 'asc' }, + }); + } + + listProducts(query: { + storeId?: string; + storeSlug?: string; + name?: string; + categoryId?: string; + minPrice?: number; + maxPrice?: number; + }) { + const where: Prisma.ProductWhereInput = { + published: true, + store: { status: StoreStatus.active }, + ...(query.storeId ? { storeId: query.storeId } : {}), + ...(query.storeSlug ? { store: { slug: query.storeSlug, status: StoreStatus.active } } : {}), + ...(query.categoryId ? { categoryId: query.categoryId } : {}), + ...(query.name + ? { + OR: [ + { nameFa: { contains: query.name, mode: 'insensitive' } }, + { nameEn: { contains: query.name, mode: 'insensitive' } }, + ], + } + : {}), + ...(query.minPrice !== undefined || query.maxPrice !== undefined + ? { + price: { + ...(query.minPrice !== undefined ? { gte: query.minPrice } : {}), + ...(query.maxPrice !== undefined ? { lte: query.maxPrice } : {}), + }, + } + : {}), + }; + + return this.prisma.product.findMany({ + where, + include: { + variants: true, + store: { + select: { id: true, slug: true, nameFa: true, logoUrl: true }, + }, + category: { select: { id: true, nameFa: true } }, + brand: { select: { id: true, nameFa: true, imageUrl: true } }, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async findProduct(id: string) { + const product = await this.prisma.product.findFirst({ + where: { + id, + published: true, + store: { status: StoreStatus.active }, + }, + include: { + variants: true, + store: { + select: { + id: true, + slug: true, + nameFa: true, + nameEn: true, + logoUrl: true, + }, + }, + category: true, + brand: true, + }, + }); + if (!product) { + throw new NotFoundException('محصول یافت نشد'); + } + return product; + } + + listBlogs(storeSlug?: string) { + return this.prisma.blogPost.findMany({ + where: { + published: true, + store: { + status: StoreStatus.active, + ...(storeSlug ? { slug: storeSlug } : {}), + }, + }, + include: { + store: { select: { id: true, slug: true, nameFa: true } }, + }, + orderBy: { createdAt: 'desc' }, + }); + } +} 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..fd7d008 --- /dev/null +++ b/src/sms/sms.service.ts @@ -0,0 +1,92 @@ +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 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..dbd9432 --- /dev/null +++ b/src/storage/storage-keys.ts @@ -0,0 +1,41 @@ +/** + * Parspack S3 object key layout (same bucket as Mado/Balout): + * + * mana-collection/ + * products/main/{uuid}{ext} + * products/gallery/{uuid}{ext} + * blogs/{uuid}{ext} + * brands/{uuid}{ext} + * users/{uuid}{ext} + * stores/{uuid}{ext} + * temp/{uuid}{ext} + */ +const ROOT = 'mana-collection'; + +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 brandMediaKey(fileName: string): string { + return `${ROOT}/brands/${fileName}`; +} + +export function userMediaKey(fileName: string): string { + return `${ROOT}/users/${fileName}`; +} + +export function storeMediaKey(fileName: string): string { + return `${ROOT}/stores/${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/src/stores/dto/store.dto.ts b/src/stores/dto/store.dto.ts new file mode 100644 index 0000000..580dad6 --- /dev/null +++ b/src/stores/dto/store.dto.ts @@ -0,0 +1,88 @@ +import { + IsOptional, + IsString, + Matches, + MinLength, + ValidateIf, +} from 'class-validator'; + +export class StoreRegisterSendCodeDto { + @IsString() + @MinLength(1) + storeNameFa!: string; + + @IsString() + @MinLength(1) + storeNameEn!: string; + + @IsString() + @MinLength(2) + @Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { + message: 'اسلاگ باید فقط حروف کوچک انگلیسی، عدد و خط تیره باشد', + }) + slug!: string; + + @IsString() + @MinLength(1) + firstName!: string; + + @IsString() + @MinLength(1) + lastName!: string; + + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @MinLength(4) + password!: string; + + @IsOptional() + @IsString() + description?: string; +} + +export class StoreRegisterVerifyDto { + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; + + @IsString() + @MinLength(4) + code!: string; +} + +export class UpdateStoreMeDto { + @IsOptional() + @IsString() + @MinLength(1) + nameFa?: string; + + @IsOptional() + @IsString() + nameEn?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + logoUrl?: string; + + @IsOptional() + @IsString() + logoKey?: string; +} + +export class UpdateStoreStatusDto { + @IsString() + @Matches(/^(pending|active|rejected|suspended)$/) + status!: 'pending' | 'active' | 'rejected' | 'suspended'; + + @ValidateIf((o: UpdateStoreStatusDto) => o.status === 'rejected') + @IsOptional() + @IsString() + rejectionReason?: string; +} diff --git a/src/stores/stores.controller.ts b/src/stores/stores.controller.ts new file mode 100644 index 0000000..acb74b5 --- /dev/null +++ b/src/stores/stores.controller.ts @@ -0,0 +1,66 @@ +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { StoreStatus, UserRole } from '@prisma/client'; +import { CurrentUser } from '../auth/decorators/current-user.decorator'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import type { AuthUser } from '../auth/auth.types'; +import { + StoreRegisterSendCodeDto, + StoreRegisterVerifyDto, + UpdateStoreMeDto, + UpdateStoreStatusDto, +} from './dto/store.dto'; +import { StoresService } from './stores.service'; + +@Controller() +export class StoresController { + constructor(private readonly stores: StoresService) {} + + @Post('public/stores/register/send-code') + registerSendCode(@Body() dto: StoreRegisterSendCodeDto) { + return this.stores.registerSendCode(dto); + } + + @Post('public/stores/register/verify') + registerVerify(@Body() dto: StoreRegisterVerifyDto) { + return this.stores.registerVerify(dto); + } + + @Get('stores/me') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.STORE_ADMIN) + getMe(@CurrentUser() user: AuthUser) { + return this.stores.getMe(user); + } + + @Patch('stores/me') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.STORE_ADMIN) + updateMe(@CurrentUser() user: AuthUser, @Body() dto: UpdateStoreMeDto) { + return this.stores.updateMe(user, dto); + } + + @Get('admin/stores') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.SUPER_ADMIN) + listAdmin(@Query('status') status?: StoreStatus) { + return this.stores.listAdmin(status); + } + + @Patch('admin/stores/:id/status') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(UserRole.SUPER_ADMIN) + updateStatus(@Param('id') id: string, @Body() dto: UpdateStoreStatusDto) { + return this.stores.updateStatus(id, dto); + } +} diff --git a/src/stores/stores.module.ts b/src/stores/stores.module.ts new file mode 100644 index 0000000..0a0cc2a --- /dev/null +++ b/src/stores/stores.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from '../auth/auth.module'; +import { StoreScopeService } from '../common/store-scope.service'; +import { SmsModule } from '../sms/sms.module'; +import { StoresController } from './stores.controller'; +import { StoresService } from './stores.service'; + +@Module({ + imports: [AuthModule, SmsModule], + controllers: [StoresController], + providers: [StoresService, StoreScopeService], + exports: [StoresService, StoreScopeService], +}) +export class StoresModule {} diff --git a/src/stores/stores.service.ts b/src/stores/stores.service.ts new file mode 100644 index 0000000..398d719 --- /dev/null +++ b/src/stores/stores.service.ts @@ -0,0 +1,211 @@ +import { + BadRequestException, + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { + SmsOtpPurpose, + StoreStatus, + UserRole, +} from '@prisma/client'; +import * as bcrypt from 'bcrypt'; +import { AuthService } from '../auth/auth.service'; +import { AuthUser } from '../auth/auth.types'; +import { StoreScopeService } from '../common/store-scope.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { SmsService } from '../sms/sms.service'; +import { + StoreRegisterSendCodeDto, + StoreRegisterVerifyDto, + UpdateStoreMeDto, + UpdateStoreStatusDto, +} from './dto/store.dto'; + +type StoreRegisterPayload = { + storeNameFa: string; + storeNameEn: string; + slug: string; + firstName: string; + lastName: string; + passwordHash: string; + description?: string; +}; + +@Injectable() +export class StoresService { + constructor( + private readonly prisma: PrismaService, + private readonly auth: AuthService, + private readonly storeScope: StoreScopeService, + private readonly sms: SmsService, + ) {} + + async registerSendCode(dto: StoreRegisterSendCodeDto) { + const slug = dto.slug.trim().toLowerCase(); + const existingUser = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + select: { id: true }, + }); + if (existingUser) { + throw new ConflictException('این شماره قبلاً ثبت شده است'); + } + + const existingSlug = await this.prisma.store.findUnique({ + where: { slug }, + select: { id: true }, + }); + if (existingSlug) { + throw new ConflictException('این اسلاگ قبلاً استفاده شده است'); + } + + const passwordHash = await bcrypt.hash(dto.password, 10); + const payload: StoreRegisterPayload = { + storeNameFa: dto.storeNameFa.trim(), + storeNameEn: dto.storeNameEn.trim(), + slug, + firstName: dto.firstName.trim(), + lastName: dto.lastName.trim(), + passwordHash, + description: dto.description?.trim(), + }; + + return this.auth.createAndSendOtpPublic({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.storeRegister, + payload, + message: (code) => `کد ثبت فروشگاه مانا کالکشن: ${code}`, + }); + } + + async registerVerify(dto: StoreRegisterVerifyDto) { + const otp = await this.auth.assertValidOtpPublic({ + cellNumber: dto.cellNumber, + purpose: SmsOtpPurpose.storeRegister, + code: dto.code, + consume: true, + }); + + const payload = otp.payload as StoreRegisterPayload | null; + if ( + !payload?.storeNameFa || + !payload?.slug || + !payload?.firstName || + !payload?.lastName || + !payload?.passwordHash + ) { + throw new BadRequestException( + 'اطلاعات ثبت فروشگاه نامعتبر است. دوباره شروع کنید', + ); + } + + const existingUser = await this.prisma.user.findUnique({ + where: { cellNumber: dto.cellNumber }, + select: { id: true }, + }); + if (existingUser) { + throw new ConflictException('این شماره قبلاً ثبت شده است'); + } + + const existingSlug = await this.prisma.store.findUnique({ + where: { slug: payload.slug }, + select: { id: true }, + }); + if (existingSlug) { + throw new ConflictException('این اسلاگ قبلاً استفاده شده است'); + } + + const result = await this.prisma.$transaction(async (tx) => { + const user = await tx.user.create({ + data: { + firstName: payload.firstName, + lastName: payload.lastName, + cellNumber: dto.cellNumber, + passwordHash: payload.passwordHash, + role: UserRole.STORE_ADMIN, + }, + }); + + const store = await tx.store.create({ + data: { + slug: payload.slug, + nameFa: payload.storeNameFa, + nameEn: payload.storeNameEn ?? '', + description: payload.description, + status: StoreStatus.pending, + ownerId: user.id, + }, + }); + + return { user, store }; + }); + + await this.sms.notifyAdmins( + `درخواست فروشگاه جدید «${result.store.nameFa}» از ${result.user.firstName} ${result.user.lastName} (${result.user.cellNumber}) ثبت شد.\nمانا کالکشن.`, + result.user.cellNumber, + ); + + return this.auth.issueTokensPublic({ + ...result.user, + ownedStore: { id: result.store.id }, + }); + } + + async getMe(actor: AuthUser) { + const store = await this.storeScope.requireOwnedStore(actor); + return store; + } + + async updateMe(actor: AuthUser, dto: UpdateStoreMeDto) { + const store = await this.storeScope.requireOwnedStore(actor); + return this.prisma.store.update({ + where: { id: store.id }, + data: { + ...(dto.nameFa !== undefined ? { nameFa: dto.nameFa.trim() } : {}), + ...(dto.nameEn !== undefined ? { nameEn: dto.nameEn.trim() } : {}), + ...(dto.description !== undefined + ? { description: dto.description } + : {}), + ...(dto.logoUrl !== undefined ? { logoUrl: dto.logoUrl } : {}), + ...(dto.logoKey !== undefined ? { logoKey: dto.logoKey } : {}), + }, + }); + } + + listAdmin(status?: StoreStatus) { + return this.prisma.store.findMany({ + where: status ? { status } : undefined, + include: { + owner: { + select: { + id: true, + firstName: true, + lastName: true, + cellNumber: true, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async updateStatus(id: string, dto: UpdateStoreStatusDto) { + const store = await this.prisma.store.findUnique({ where: { id } }); + if (!store) { + throw new NotFoundException('فروشگاه یافت نشد'); + } + + return this.prisma.store.update({ + where: { id }, + data: { + status: dto.status as StoreStatus, + rejectionReason: + dto.status === 'rejected' + ? (dto.rejectionReason ?? store.rejectionReason) + : dto.status === 'active' + ? null + : store.rejectionReason, + }, + }); + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..052134c --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "scripts"] +} 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 + } +}