From 7de04ab4e0622fc475fc17c42b396c986409e711 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Sun, 29 Mar 2026 17:31:58 +0530 Subject: [PATCH] chore: bootstrap uber eats wrapper scaffold with docs and openapi --- .env.example | 16 + .gitignore | 5 + README.md | 76 + docs/developer-portal/01-overview.md | 20 + docs/developer-portal/02-api-groups.md | 31 + docs/developer-portal/02-auth-oauth.md | 9 + docs/developer-portal/03-integration-flow.md | 52 + .../03-merchant-onboarding.md | 11 + .../04-extended-api-catalog-template.md | 25 + docs/developer-portal/04-stores.md | 9 + docs/developer-portal/05-menus.md | 9 + docs/developer-portal/06-orders.md | 10 + docs/developer-portal/07-webhooks.md | 11 + docs/developer-portal/08-delivery-status.md | 8 + docs/developer-portal/09-errors-retries.md | 9 + docs/developer-portal/10-sandbox-testing.md | 10 + .../developer-portal/11-production-go-live.md | 9 + docs/developer-portal/12-sdk-wrapper.md | 9 + docs/developer-portal/13-pos-mapping-spec.md | 9 + docs/developer-portal/14-openapi-swagger.md | 9 + docs/developer-portal/15-postman.md | 12 + docs/openapi/README.md | 8 + docs/openapi/openapi.json | 251 ++ docs/optional-direct-delivery/README.md | 7 + package-lock.json | 2209 +++++++++++++++++ package.json | 32 + postman/Uber_Wrapper.postman_collection.json | 138 + scripts/export-openapi.js | 16 + src/app.js | 34 + src/config/env.js | 27 + src/config/uberEndpoints.js | 23 + src/db/adapter.js | 14 + src/db/repositories.js | 160 ++ src/db/sqlite.js | 75 + src/docs/swagger.js | 32 + src/middleware/asyncHandler.js | 10 + src/middleware/errorHandler.js | 20 + src/middleware/requestContext.js | 30 + src/modules/auth/auth.controller.js | 92 + src/modules/auth/auth.service.js | 61 + .../connections/connections.controller.js | 54 + src/modules/health/health.controller.js | 15 + src/modules/proxy/proxy.controller.js | 97 + src/modules/proxy/proxy.service.js | 161 ++ src/modules/webhooks/webhooks.controller.js | 25 + src/routes/auth.routes.js | 59 + src/routes/connections.routes.js | 50 + src/routes/health.routes.js | 21 + src/routes/proxy.routes.js | 92 + src/routes/webhooks.routes.js | 21 + src/server.js | 11 + 51 files changed, 4204 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/developer-portal/01-overview.md create mode 100644 docs/developer-portal/02-api-groups.md create mode 100644 docs/developer-portal/02-auth-oauth.md create mode 100644 docs/developer-portal/03-integration-flow.md create mode 100644 docs/developer-portal/03-merchant-onboarding.md create mode 100644 docs/developer-portal/04-extended-api-catalog-template.md create mode 100644 docs/developer-portal/04-stores.md create mode 100644 docs/developer-portal/05-menus.md create mode 100644 docs/developer-portal/06-orders.md create mode 100644 docs/developer-portal/07-webhooks.md create mode 100644 docs/developer-portal/08-delivery-status.md create mode 100644 docs/developer-portal/09-errors-retries.md create mode 100644 docs/developer-portal/10-sandbox-testing.md create mode 100644 docs/developer-portal/11-production-go-live.md create mode 100644 docs/developer-portal/12-sdk-wrapper.md create mode 100644 docs/developer-portal/13-pos-mapping-spec.md create mode 100644 docs/developer-portal/14-openapi-swagger.md create mode 100644 docs/developer-portal/15-postman.md create mode 100644 docs/openapi/README.md create mode 100644 docs/openapi/openapi.json create mode 100644 docs/optional-direct-delivery/README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postman/Uber_Wrapper.postman_collection.json create mode 100644 scripts/export-openapi.js create mode 100644 src/app.js create mode 100644 src/config/env.js create mode 100644 src/config/uberEndpoints.js create mode 100644 src/db/adapter.js create mode 100644 src/db/repositories.js create mode 100644 src/db/sqlite.js create mode 100644 src/docs/swagger.js create mode 100644 src/middleware/asyncHandler.js create mode 100644 src/middleware/errorHandler.js create mode 100644 src/middleware/requestContext.js create mode 100644 src/modules/auth/auth.controller.js create mode 100644 src/modules/auth/auth.service.js create mode 100644 src/modules/connections/connections.controller.js create mode 100644 src/modules/health/health.controller.js create mode 100644 src/modules/proxy/proxy.controller.js create mode 100644 src/modules/proxy/proxy.service.js create mode 100644 src/modules/webhooks/webhooks.controller.js create mode 100644 src/routes/auth.routes.js create mode 100644 src/routes/connections.routes.js create mode 100644 src/routes/health.routes.js create mode 100644 src/routes/proxy.routes.js create mode 100644 src/routes/webhooks.routes.js create mode 100644 src/server.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4916a3b --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +NODE_ENV=development +PORT=8080 + +# Uber app credentials +UBER_CLIENT_ID=your_client_id +UBER_CLIENT_SECRET=your_client_secret +UBER_REDIRECT_URI=http://localhost:8080/api/v1/auth/uber/callback +UBER_OAUTH_BASE_URL=https://login.uber.com +UBER_API_BASE_URL=https://api.uber.com + +# SQLite database path +SQLITE_PATH=./data/uber_wrapper.db + +# Shared API key for wrapper clients (optional but recommended) +WRAPPER_API_KEY=change-me + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a34221 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.env +data/ +npm-debug.log* + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5ce47f --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# Uber Wrapper (Node.js + Express + SQLite) + +Generic multi-merchant Uber Eats API wrapper for POS integrations. + +## What this gives you + +- Multi-merchant model (each restaurant connects its own Uber account) +- OAuth flow support and manual token storage support +- Generic passthrough endpoint to cover all Uber APIs +- Business shortcuts for common menu/order/store flows +- Webhook ingestion endpoint +- SQLite persistence with adapter boundary for future MySQL/Postgres migration +- Swagger UI docs + Postman collection + +## Quick Start + +1. Install dependencies + +```bash +npm install +``` + +2. Configure env + +```bash +copy .env.example .env +``` + +3. Start server + +```bash +npm run dev +``` + +4. Open docs + +- Swagger UI: `http://localhost:8080/docs` +- Health: `http://localhost:8080/health` + +## Folder Structure + +- `src/` application source +- `src/db/adapter.js` database abstraction boundary +- `docs/developer-portal/` human-friendly docs +- `docs/openapi/` exported OpenAPI artifacts +- `postman/` Postman collection + +## Database Migration Strategy + +All controllers/services consume repositories through `src/db/adapter.js`. +To switch from SQLite to MySQL/Postgres later: + +1. Implement repository methods for new DB backend. +2. Replace exports in `src/db/adapter.js`. +3. Keep controllers/routes unchanged. + +## Scope (Current Phase) + +Current implementation focus is **Uber Eats Marketplace APIs only**: + +- Authentication (OAuth) +- Stores +- Menus +- Orders +- Marketplace webhooks + +Uber Direct is intentionally deferred to a later phase. + +## Important Note About Endpoint Coverage + +This wrapper includes: + +- A generic endpoint (`POST /api/v1/uber/request`) for all current/future **Uber Eats** endpoints. +- Pre-built shortcuts for high-frequency flows (menu/orders/store hours). + +When you finalize your Uber docs copy, update `src/config/uberEndpoints.js` with exact endpoint paths from your approved Uber docs. diff --git a/docs/developer-portal/01-overview.md b/docs/developer-portal/01-overview.md new file mode 100644 index 0000000..8adc5ce --- /dev/null +++ b/docs/developer-portal/01-overview.md @@ -0,0 +1,20 @@ +# Uber Wrapper Developer Portal + +## Goal + +Create one generic POS-facing wrapper that can integrate with Uber for many restaurants, each with its own Uber OAuth connection. + +## Architecture Summary + +1. Merchant is created in wrapper. +2. Merchant connects Uber account via OAuth. +3. Wrapper stores tokens and proxies requests to Uber APIs. +4. POS uses wrapper APIs only. +5. Uber webhooks are ingested by wrapper and routed to internal logic. + +## Key Benefits + +- Your POS is isolated from Uber API complexity. +- You can add custom business logic centrally. +- You can support many merchants with one standard integration path. + diff --git a/docs/developer-portal/02-api-groups.md b/docs/developer-portal/02-api-groups.md new file mode 100644 index 0000000..73b60d3 --- /dev/null +++ b/docs/developer-portal/02-api-groups.md @@ -0,0 +1,31 @@ +# API Groups + +This file intentionally separates high-priority vs extended APIs for easier team onboarding. + +## Group A (Important for POS Core) + +- Merchant management +- OAuth connection flow +- Menu upsert/get +- Order pull/list +- Order actions (accept/deny/ready/cancel) +- Store hours updates +- Uber webhook ingestion + +## Group B (Extended / Optional / Can Be Added Incrementally) + +- Promotions +- Ads / sponsored listings +- Payout and financial reconciliation +- Store holiday/special schedules +- Catalog media sync +- Audit/event feed replay + +## Group C (Generic Catch-All) + +Use: + +- `POST /api/v1/uber/request` + +This endpoint is the fallback for any Uber endpoint that is not yet added as a typed shortcut route. + diff --git a/docs/developer-portal/02-auth-oauth.md b/docs/developer-portal/02-auth-oauth.md new file mode 100644 index 0000000..bace397 --- /dev/null +++ b/docs/developer-portal/02-auth-oauth.md @@ -0,0 +1,9 @@ +# 02 Auth OAuth + +Focus: Uber Eats OAuth 2.0 per merchant (multi-client onboarding). + +- Generate authorize URL by merchant +- Handle callback and store tokens +- Refresh token flow +- Token expiry handling policy + diff --git a/docs/developer-portal/03-integration-flow.md b/docs/developer-portal/03-integration-flow.md new file mode 100644 index 0000000..9635e23 --- /dev/null +++ b/docs/developer-portal/03-integration-flow.md @@ -0,0 +1,52 @@ +# Integration Flow + +## 1. Create Merchant + +`POST /api/v1/merchants` + +```json +{ + "name": "My Restaurant", + "externalRef": "pos_store_101" +} +``` + +## 2. Get OAuth URL + +`GET /api/v1/auth/uber/authorize-url?merchantId=` + +Redirect merchant to returned URL. + +## 3. OAuth Callback + +Uber redirects to: + +`GET /api/v1/auth/uber/callback?code=...&state=...` + +Wrapper stores merchant token. + +## 4. Push Menu + +`POST /api/v1/uber/menu/upsert` + +## 5. Pull Orders + +`GET /api/v1/uber/orders?merchantId=...&storeId=...` + +## 6. Receive Webhooks + +`POST /api/v1/webhooks/uber` + +## 7. Use Generic API for Any Missing Endpoint + +`POST /api/v1/uber/request` + +```json +{ + "merchantId": "", + "method": "POST", + "path": "/v1/eats/some/new/uber/endpoint", + "body": {} +} +``` + diff --git a/docs/developer-portal/03-merchant-onboarding.md b/docs/developer-portal/03-merchant-onboarding.md new file mode 100644 index 0000000..9a756b5 --- /dev/null +++ b/docs/developer-portal/03-merchant-onboarding.md @@ -0,0 +1,11 @@ +# 03 Merchant Onboarding + +Flow: + +1. Create merchant in wrapper +2. Generate Uber OAuth URL +3. Merchant authorizes Uber account +4. Callback stores tokens + store identifiers + +Multi-client principle: each merchant has isolated credentials and mappings. + diff --git a/docs/developer-portal/04-extended-api-catalog-template.md b/docs/developer-portal/04-extended-api-catalog-template.md new file mode 100644 index 0000000..9fa2ef1 --- /dev/null +++ b/docs/developer-portal/04-extended-api-catalog-template.md @@ -0,0 +1,25 @@ +# Extended API Catalog Template + +Use this file to paste the full Uber documentation mappings that are not in core POS flows. + +## How to document each endpoint + +1. Wrapper route +2. Uber upstream route +3. HTTP method +4. Required merchant/store identifiers +5. Request payload schema +6. Response schema +7. Retry/idempotency behavior +8. Error mapping + +## Suggested Sections + +- Catalog and item metadata +- Option groups and modifier sets +- Promotions and pricing campaigns +- Financial, payouts, settlements +- Delivery fulfillment edge cases +- Store operational settings +- Reporting and analytics data pulls + diff --git a/docs/developer-portal/04-stores.md b/docs/developer-portal/04-stores.md new file mode 100644 index 0000000..9046ff1 --- /dev/null +++ b/docs/developer-portal/04-stores.md @@ -0,0 +1,9 @@ +# 04 Stores + +Uber Eats store-focused integrations: + +- Store details retrieval +- Store status +- Holiday/special hours +- Operational metadata sync + diff --git a/docs/developer-portal/05-menus.md b/docs/developer-portal/05-menus.md new file mode 100644 index 0000000..cd81adf --- /dev/null +++ b/docs/developer-portal/05-menus.md @@ -0,0 +1,9 @@ +# 05 Menus + +Menu sync between POS and Uber Eats: + +- Upload/replace menu +- Fetch menu from Uber +- Item and modifier mapping strategy +- Validation and publish error handling + diff --git a/docs/developer-portal/06-orders.md b/docs/developer-portal/06-orders.md new file mode 100644 index 0000000..56a0396 --- /dev/null +++ b/docs/developer-portal/06-orders.md @@ -0,0 +1,10 @@ +# 06 Orders + +Order flow for POS: + +- Receive order event +- Fetch full order payload +- Accept/deny +- Ready/handoff updates +- Completion/cancellation reconciliation + diff --git a/docs/developer-portal/07-webhooks.md b/docs/developer-portal/07-webhooks.md new file mode 100644 index 0000000..335736b --- /dev/null +++ b/docs/developer-portal/07-webhooks.md @@ -0,0 +1,11 @@ +# 07 Webhooks + +Webhook design goals: + +- Multi-merchant event routing +- Idempotent processing +- Retry-safe handlers +- Event logging and replay support + +Current ingestion endpoint: `POST /api/v1/webhooks/uber` + diff --git a/docs/developer-portal/08-delivery-status.md b/docs/developer-portal/08-delivery-status.md new file mode 100644 index 0000000..e33cfbe --- /dev/null +++ b/docs/developer-portal/08-delivery-status.md @@ -0,0 +1,8 @@ +# 08 Delivery Status + +Delivery-related updates consumed from Uber Eats order/webhook lifecycle: + +- Courier assignment milestones +- Pickup and dropoff transitions +- Final completion state mapping + diff --git a/docs/developer-portal/09-errors-retries.md b/docs/developer-portal/09-errors-retries.md new file mode 100644 index 0000000..3286a32 --- /dev/null +++ b/docs/developer-portal/09-errors-retries.md @@ -0,0 +1,9 @@ +# 09 Errors Retries + +Standards: + +- Normalize Uber error responses +- Define retryable vs non-retryable errors +- Enforce idempotent writes where possible +- Capture request/response logs for debugging + diff --git a/docs/developer-portal/10-sandbox-testing.md b/docs/developer-portal/10-sandbox-testing.md new file mode 100644 index 0000000..f0894c0 --- /dev/null +++ b/docs/developer-portal/10-sandbox-testing.md @@ -0,0 +1,10 @@ +# 10 Sandbox Testing + +Checklist: + +- Test OAuth connect flow +- Test menu upload/get +- Test order lifecycle actions +- Test webhook receipt and persistence +- Validate retry and duplicate event handling + diff --git a/docs/developer-portal/11-production-go-live.md b/docs/developer-portal/11-production-go-live.md new file mode 100644 index 0000000..9277664 --- /dev/null +++ b/docs/developer-portal/11-production-go-live.md @@ -0,0 +1,9 @@ +# 11 Production Go Live + +Go-live readiness: + +- Production OAuth credentials configured +- Webhook URL reachable over HTTPS +- Alerting and log monitoring enabled +- Token refresh and failure runbooks ready + diff --git a/docs/developer-portal/12-sdk-wrapper.md b/docs/developer-portal/12-sdk-wrapper.md new file mode 100644 index 0000000..5937907 --- /dev/null +++ b/docs/developer-portal/12-sdk-wrapper.md @@ -0,0 +1,9 @@ +# 12 SDK Wrapper + +Internal SDK goals: + +- Stable wrapper methods for POS services +- Central auth/token handling +- Typed request/response contracts +- Consistent retries and error translation + diff --git a/docs/developer-portal/13-pos-mapping-spec.md b/docs/developer-portal/13-pos-mapping-spec.md new file mode 100644 index 0000000..79a45f5 --- /dev/null +++ b/docs/developer-portal/13-pos-mapping-spec.md @@ -0,0 +1,9 @@ +# 13 POS Mapping Spec + +Define canonical mappings: + +- POS store ID <-> Uber store ID +- POS item/modifier IDs <-> Uber external IDs +- POS order status <-> Uber order state +- POS tax/fees fields <-> Uber charge lines + diff --git a/docs/developer-portal/14-openapi-swagger.md b/docs/developer-portal/14-openapi-swagger.md new file mode 100644 index 0000000..d440a18 --- /dev/null +++ b/docs/developer-portal/14-openapi-swagger.md @@ -0,0 +1,9 @@ +# 14 OpenAPI Swagger + +Docs sources: + +- Live docs: `/docs` +- Static export: `docs/openapi/openapi.json` + +Update route annotations whenever endpoints are added/changed. + diff --git a/docs/developer-portal/15-postman.md b/docs/developer-portal/15-postman.md new file mode 100644 index 0000000..1f137a7 --- /dev/null +++ b/docs/developer-portal/15-postman.md @@ -0,0 +1,12 @@ +# 15 Postman + +Collection file: + +- `postman/Uber_Wrapper.postman_collection.json` + +Keep environments for: + +- local +- sandbox +- production + diff --git a/docs/openapi/README.md b/docs/openapi/README.md new file mode 100644 index 0000000..d85ef46 --- /dev/null +++ b/docs/openapi/README.md @@ -0,0 +1,8 @@ +# OpenAPI Export + +Live OpenAPI spec is served from route annotations at runtime: + +- `GET /docs` + +If you need a static JSON export for CI or portal import, generate it from `src/docs/swagger.js` in your build script. + diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json new file mode 100644 index 0000000..ad054e5 --- /dev/null +++ b/docs/openapi/openapi.json @@ -0,0 +1,251 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Uber Wrapper API", + "version": "1.0.0", + "description": "Generic multi-merchant Uber API wrapper for POS platforms" + }, + "servers": [ + { + "url": "http://localhost:8080" + } + ], + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "paths": { + "/api/v1/auth/uber/authorize-url": { + "get": { + "summary": "Generate Uber OAuth authorize URL for merchant", + "tags": [ + "Auth" + ], + "parameters": [ + { + "in": "query", + "name": "merchantId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OAuth URL generated" + } + } + } + }, + "/api/v1/auth/uber/callback": { + "get": { + "summary": "Uber OAuth callback endpoint", + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "Uber account connected" + } + } + } + }, + "/api/v1/auth/uber/{merchantId}/refresh-token": { + "post": { + "summary": "Refresh Uber token for merchant", + "tags": [ + "Auth" + ], + "parameters": [ + { + "in": "path", + "name": "merchantId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Token refreshed" + } + } + } + }, + "/api/v1/merchants": { + "post": { + "summary": "Create or update merchant", + "tags": [ + "Merchants" + ], + "responses": { + "201": { + "description": "Merchant upserted" + } + } + }, + "get": { + "summary": "List merchants", + "tags": [ + "Merchants" + ], + "responses": { + "200": { + "description": "Merchant list" + } + } + } + }, + "/api/v1/connections/uber": { + "post": { + "summary": "Save manual Uber tokens for merchant", + "tags": [ + "Connections" + ], + "responses": { + "201": { + "description": "Connection stored" + } + } + }, + "get": { + "summary": "List Uber connections", + "tags": [ + "Connections" + ], + "responses": { + "200": { + "description": "Connection list" + } + } + } + }, + "/health": { + "get": { + "summary": "Health check", + "tags": [ + "Health" + ], + "responses": { + "200": { + "description": "Service is healthy" + } + } + } + }, + "/api/v1/uber/request": { + "post": { + "summary": "Generic Uber passthrough for any Uber endpoint", + "tags": [ + "Uber Generic" + ], + "responses": { + "200": { + "description": "Uber response" + } + } + } + }, + "/api/v1/uber/menu/upsert": { + "post": { + "summary": "Upsert store menu", + "tags": [ + "Uber Menu" + ], + "responses": { + "200": { + "description": "Menu upserted" + } + } + } + }, + "/api/v1/uber/menu": { + "get": { + "summary": "Fetch store menu", + "tags": [ + "Uber Menu" + ], + "responses": { + "200": { + "description": "Menu fetched" + } + } + } + }, + "/api/v1/uber/orders": { + "get": { + "summary": "List store orders", + "tags": [ + "Uber Orders" + ], + "responses": { + "200": { + "description": "Orders fetched" + } + } + } + }, + "/api/v1/uber/orders/{orderId}/action": { + "post": { + "summary": "Trigger order action (accept, deny, ready, cancel)", + "tags": [ + "Uber Orders" + ], + "parameters": [ + { + "in": "path", + "name": "orderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Order action sent" + } + } + } + }, + "/api/v1/uber/stores/hours": { + "put": { + "summary": "Update store hours", + "tags": [ + "Uber Stores" + ], + "responses": { + "200": { + "description": "Store hours updated" + } + } + } + }, + "/api/v1/webhooks/uber": { + "post": { + "summary": "Ingest Uber webhook events", + "tags": [ + "Webhooks" + ], + "responses": { + "202": { + "description": "Webhook accepted" + } + } + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/docs/optional-direct-delivery/README.md b/docs/optional-direct-delivery/README.md new file mode 100644 index 0000000..1d014d1 --- /dev/null +++ b/docs/optional-direct-delivery/README.md @@ -0,0 +1,7 @@ +# Optional Direct Delivery (Later) + +This folder is intentionally separate from Uber Eats Marketplace scope. + +Do not implement Uber Direct in current phase. +Add Uber Direct documentation and endpoints here only when Uber Eats scope is complete. + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3fc70a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2209 @@ +{ + "name": "uber_wrapper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "uber_wrapper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.14.0", + "better-sqlite3": "^12.8.0", + "cors": "^2.8.6", + "dotenv": "^17.3.1", + "express": "^5.2.1", + "helmet": "^8.1.0", + "morgan": "^1.10.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "uuid": "^13.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "nodemon": "^3.1.14" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/better-sqlite3": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", + "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "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/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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "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/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "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/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "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-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/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/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, + "node_modules/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.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/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/nodemon/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/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/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "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.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "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/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_decoder/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/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.32.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.1.tgz", + "integrity": "sha512-6HQoo7+j8PA2QqP5kgAb9dl1uxUjvR0SAoL/WUp1sTEvm0F6D5npgU2OGCLwl++bIInqGlEUQ2mpuZRZYtyCzQ==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..413aa54 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "uber_wrapper", + "version": "1.0.0", + "description": "Generic multi-merchant Uber API wrapper for POS integrations", + "main": "src/server.js", + "scripts": { + "dev": "nodemon src/server.js", + "start": "node src/server.js", + "openapi:export": "node scripts/export-openapi.js", + "test": "echo \"No tests configured yet\"" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "axios": "^1.14.0", + "better-sqlite3": "^12.8.0", + "cors": "^2.8.6", + "dotenv": "^17.3.1", + "express": "^5.2.1", + "helmet": "^8.1.0", + "morgan": "^1.10.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "uuid": "^13.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "nodemon": "^3.1.14" + } +} diff --git a/postman/Uber_Wrapper.postman_collection.json b/postman/Uber_Wrapper.postman_collection.json new file mode 100644 index 0000000..4023ba8 --- /dev/null +++ b/postman/Uber_Wrapper.postman_collection.json @@ -0,0 +1,138 @@ +{ + "info": { + "_postman_id": "3f2bc407-ec9d-4eef-b77d-74359eb281fe", + "name": "Uber Wrapper", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Health", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/health", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] + } + } + }, + { + "name": "Create Merchant", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Demo Restaurant\",\n \"externalRef\": \"pos_001\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/merchants", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "merchants" + ] + } + } + }, + { + "name": "Get OAuth URL", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v1/auth/uber/authorize-url?merchantId={{merchantId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "auth", + "uber", + "authorize-url" + ], + "query": [ + { + "key": "merchantId", + "value": "{{merchantId}}" + } + ] + } + } + }, + { + "name": "Generic Uber Request", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"method\": \"GET\",\n \"path\": \"/v1/eats/stores/{{storeId}}/orders\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/request", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "request" + ] + } + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080" + }, + { + "key": "apiKey", + "value": "change-me" + }, + { + "key": "merchantId", + "value": "" + }, + { + "key": "storeId", + "value": "" + } + ] +} + diff --git a/scripts/export-openapi.js b/scripts/export-openapi.js new file mode 100644 index 0000000..95f5a6a --- /dev/null +++ b/scripts/export-openapi.js @@ -0,0 +1,16 @@ +require("dotenv").config(); +const fs = require("fs"); +const path = require("path"); +const spec = require("../src/docs/swagger"); + +const outputDir = path.resolve(process.cwd(), "docs", "openapi"); +const outputFile = path.join(outputDir, "openapi.json"); + +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +fs.writeFileSync(outputFile, JSON.stringify(spec, null, 2), "utf8"); +// eslint-disable-next-line no-console +console.log(`OpenAPI exported to ${outputFile}`); + diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..7f48274 --- /dev/null +++ b/src/app.js @@ -0,0 +1,34 @@ +require("dotenv").config(); +const express = require("express"); +const helmet = require("helmet"); +const cors = require("cors"); +const morgan = require("morgan"); +const swaggerUi = require("swagger-ui-express"); +const spec = require("./docs/swagger"); +const { requestContext, requireWrapperApiKey } = require("./middleware/requestContext"); +const errorHandler = require("./middleware/errorHandler"); +const healthRoutes = require("./routes/health.routes"); +const authRoutes = require("./routes/auth.routes"); +const connectionRoutes = require("./routes/connections.routes"); +const proxyRoutes = require("./routes/proxy.routes"); +const webhookRoutes = require("./routes/webhooks.routes"); + +const app = express(); + +app.use(helmet()); +app.use(cors()); +app.use(express.json({ limit: "5mb" })); +app.use(morgan("combined")); +app.use(requestContext); + +app.use("/health", healthRoutes); +app.use("/api/v1/auth", requireWrapperApiKey, authRoutes); +app.use("/api/v1", requireWrapperApiKey, connectionRoutes); +app.use("/api/v1", requireWrapperApiKey, proxyRoutes); +app.use("/api/v1", webhookRoutes); +app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec)); + +app.use(errorHandler); + +module.exports = app; + diff --git a/src/config/env.js b/src/config/env.js new file mode 100644 index 0000000..3cd5b8f --- /dev/null +++ b/src/config/env.js @@ -0,0 +1,27 @@ +const path = require("path"); +const { z } = require("zod"); + +const envSchema = z.object({ + NODE_ENV: z.string().default("development"), + PORT: z.coerce.number().default(8080), + UBER_CLIENT_ID: z.string().min(1), + UBER_CLIENT_SECRET: z.string().min(1), + UBER_REDIRECT_URI: z.string().url(), + UBER_OAUTH_BASE_URL: z.string().url().default("https://login.uber.com"), + UBER_API_BASE_URL: z.string().url().default("https://api.uber.com"), + SQLITE_PATH: z.string().default("./data/uber_wrapper.db"), + WRAPPER_API_KEY: z.string().optional() +}); + +const parsed = envSchema.safeParse(process.env); + +if (!parsed.success) { + const issues = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`); + throw new Error(`Invalid environment variables:\n${issues.join("\n")}`); +} + +const env = parsed.data; +env.SQLITE_PATH = path.resolve(process.cwd(), env.SQLITE_PATH); + +module.exports = env; + diff --git a/src/config/uberEndpoints.js b/src/config/uberEndpoints.js new file mode 100644 index 0000000..93c89d2 --- /dev/null +++ b/src/config/uberEndpoints.js @@ -0,0 +1,23 @@ +module.exports = { + menu: { + upsert: "/v1/eats/stores/{storeId}/menus", + get: "/v1/eats/stores/{storeId}/menus" + }, + orders: { + list: "/v1/eats/stores/{storeId}/orders", + getById: "/v1/eats/orders/{orderId}", + accept: "/v1/eats/orders/{orderId}/accept_pos_order", + deny: "/v1/eats/orders/{orderId}/deny_pos_order", + readyForPickup: "/v1/eats/orders/{orderId}/pos_order_ready_for_pickup", + cancel: "/v1/eats/orders/{orderId}/cancel" + }, + stores: { + getById: "/v1/eats/stores/{storeId}", + updateHours: "/v1/eats/stores/{storeId}/hours", + inventory: "/v1/eats/stores/{storeId}/inventory" + }, + webhooks: { + events: "/v1/eats/stores/{storeId}/event_feed" + } +}; + diff --git a/src/db/adapter.js b/src/db/adapter.js new file mode 100644 index 0000000..07b8438 --- /dev/null +++ b/src/db/adapter.js @@ -0,0 +1,14 @@ +const repositories = require("./repositories"); + +/** + * Database adapter boundary. + * Replace exports here later with MySQL/Postgres implementations + * without changing route/controller code. + */ +module.exports = { + merchantRepository: repositories.merchantRepository, + uberConnectionRepository: repositories.uberConnectionRepository, + webhookRepository: repositories.webhookRepository, + apiLogRepository: repositories.apiLogRepository +}; + diff --git a/src/db/repositories.js b/src/db/repositories.js new file mode 100644 index 0000000..1c92f42 --- /dev/null +++ b/src/db/repositories.js @@ -0,0 +1,160 @@ +const { v4: uuidv4 } = require("uuid"); +const { db } = require("./sqlite"); + +function nowIso() { + return new Date().toISOString(); +} + +const merchantRepository = { + upsert({ id, name, externalRef }) { + const timestamp = nowIso(); + const merchantId = id || uuidv4(); + const stmt = db.prepare(` + INSERT INTO merchants (id, name, external_ref, created_at, updated_at) + VALUES (@id, @name, @external_ref, @created_at, @updated_at) + ON CONFLICT(id) DO UPDATE SET + name = excluded.name, + external_ref = excluded.external_ref, + updated_at = excluded.updated_at + `); + stmt.run({ + id: merchantId, + name, + external_ref: externalRef || null, + created_at: timestamp, + updated_at: timestamp + }); + return this.findById(merchantId); + }, + + findById(id) { + return db.prepare("SELECT * FROM merchants WHERE id = ?").get(id); + }, + + list() { + return db.prepare("SELECT * FROM merchants ORDER BY created_at DESC").all(); + } +}; + +const uberConnectionRepository = { + upsertByMerchantId(merchantId, payload) { + const existing = this.findByMerchantId(merchantId); + const timestamp = nowIso(); + const row = { + id: existing?.id || uuidv4(), + merchant_id: merchantId, + uber_user_id: payload.uberUserId || existing?.uber_user_id || null, + uber_store_id: payload.uberStoreId || existing?.uber_store_id || null, + access_token: payload.accessToken || existing?.access_token, + refresh_token: payload.refreshToken ?? existing?.refresh_token ?? null, + token_type: payload.tokenType ?? existing?.token_type ?? "Bearer", + scope: payload.scope ?? existing?.scope ?? null, + expires_at: payload.expiresAt ?? existing?.expires_at ?? null, + status: payload.status ?? existing?.status ?? "active", + created_at: existing?.created_at || timestamp, + updated_at: timestamp + }; + + const stmt = db.prepare(` + INSERT INTO uber_connections ( + id, merchant_id, uber_user_id, uber_store_id, access_token, refresh_token, + token_type, scope, expires_at, status, created_at, updated_at + ) + VALUES ( + @id, @merchant_id, @uber_user_id, @uber_store_id, @access_token, @refresh_token, + @token_type, @scope, @expires_at, @status, @created_at, @updated_at + ) + ON CONFLICT(merchant_id) DO UPDATE SET + uber_user_id = excluded.uber_user_id, + uber_store_id = excluded.uber_store_id, + access_token = excluded.access_token, + refresh_token = excluded.refresh_token, + token_type = excluded.token_type, + scope = excluded.scope, + expires_at = excluded.expires_at, + status = excluded.status, + updated_at = excluded.updated_at + `); + stmt.run(row); + return this.findByMerchantId(merchantId); + }, + + findByMerchantId(merchantId) { + return db.prepare("SELECT * FROM uber_connections WHERE merchant_id = ?").get(merchantId); + }, + + list() { + return db + .prepare(` + SELECT uc.*, m.name AS merchant_name + FROM uber_connections uc + JOIN merchants m ON m.id = uc.merchant_id + ORDER BY uc.created_at DESC + `) + .all(); + } +}; + +const webhookRepository = { + insert({ provider, merchantId, eventType, payloadJson, headersJson }) { + const row = { + id: uuidv4(), + provider, + merchant_id: merchantId || null, + event_type: eventType || null, + payload_json: JSON.stringify(payloadJson || {}), + headers_json: JSON.stringify(headersJson || {}), + received_at: nowIso(), + processing_status: "received" + }; + const stmt = db.prepare(` + INSERT INTO webhook_events ( + id, provider, merchant_id, event_type, payload_json, headers_json, + received_at, processing_status + ) + VALUES ( + @id, @provider, @merchant_id, @event_type, @payload_json, @headers_json, + @received_at, @processing_status + ) + `); + stmt.run(row); + return row; + } +}; + +const apiLogRepository = { + insert({ merchantId, method, wrapperRoute, uberPath, responseStatus, requestBody, responseBody }) { + const row = { + id: uuidv4(), + merchant_id: merchantId || null, + http_method: method, + wrapper_route: wrapperRoute, + uber_path: uberPath, + response_status: responseStatus, + request_json: JSON.stringify(requestBody || {}), + response_json: JSON.stringify(responseBody || {}), + created_at: nowIso() + }; + + const stmt = db.prepare(` + INSERT INTO api_logs ( + id, merchant_id, http_method, wrapper_route, uber_path, + response_status, request_json, response_json, created_at + ) + VALUES ( + @id, @merchant_id, @http_method, @wrapper_route, @uber_path, + @response_status, @request_json, @response_json, @created_at + ) + `); + stmt.run(row); + return row; + } +}; + +module.exports = { + merchantRepository, + uberConnectionRepository, + webhookRepository, + apiLogRepository +}; + diff --git a/src/db/sqlite.js b/src/db/sqlite.js new file mode 100644 index 0000000..8b15b5d --- /dev/null +++ b/src/db/sqlite.js @@ -0,0 +1,75 @@ +const fs = require("fs"); +const path = require("path"); +const Database = require("better-sqlite3"); +const env = require("../config/env"); + +const dbDir = path.dirname(env.SQLITE_PATH); +if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }); +} + +const db = new Database(env.SQLITE_PATH); +db.pragma("journal_mode = WAL"); + +function initSchema() { + db.exec(` + CREATE TABLE IF NOT EXISTS merchants ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + external_ref TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS uber_connections ( + id TEXT PRIMARY KEY, + merchant_id TEXT NOT NULL, + uber_user_id TEXT, + uber_store_id TEXT, + access_token TEXT NOT NULL, + refresh_token TEXT, + token_type TEXT, + scope TEXT, + expires_at TEXT, + status TEXT NOT NULL DEFAULT 'active', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY(merchant_id) REFERENCES merchants(id) + ); + + CREATE UNIQUE INDEX IF NOT EXISTS idx_uber_connections_merchant + ON uber_connections(merchant_id); + + CREATE TABLE IF NOT EXISTS webhook_events ( + id TEXT PRIMARY KEY, + provider TEXT NOT NULL, + merchant_id TEXT, + event_type TEXT, + payload_json TEXT NOT NULL, + headers_json TEXT, + received_at TEXT NOT NULL, + processed_at TEXT, + processing_status TEXT NOT NULL DEFAULT 'received', + FOREIGN KEY(merchant_id) REFERENCES merchants(id) + ); + + CREATE TABLE IF NOT EXISTS api_logs ( + id TEXT PRIMARY KEY, + merchant_id TEXT, + http_method TEXT NOT NULL, + wrapper_route TEXT NOT NULL, + uber_path TEXT NOT NULL, + response_status INTEGER, + request_json TEXT, + response_json TEXT, + created_at TEXT NOT NULL, + FOREIGN KEY(merchant_id) REFERENCES merchants(id) + ); + `); +} + +module.exports = { + db, + initSchema +}; + diff --git a/src/docs/swagger.js b/src/docs/swagger.js new file mode 100644 index 0000000..8e037e5 --- /dev/null +++ b/src/docs/swagger.js @@ -0,0 +1,32 @@ +const swaggerJsdoc = require("swagger-jsdoc"); +const env = require("../config/env"); + +const spec = swaggerJsdoc({ + definition: { + openapi: "3.0.3", + info: { + title: "Uber Wrapper API", + version: "1.0.0", + description: "Generic multi-merchant Uber API wrapper for POS platforms" + }, + servers: [ + { + url: `http://localhost:${env.PORT}` + } + ], + components: { + securitySchemes: { + ApiKeyAuth: { + type: "apiKey", + in: "header", + name: "x-api-key" + } + } + }, + security: [{ ApiKeyAuth: [] }] + }, + apis: ["./src/routes/*.js"] +}); + +module.exports = spec; + diff --git a/src/middleware/asyncHandler.js b/src/middleware/asyncHandler.js new file mode 100644 index 0000000..f435e83 --- /dev/null +++ b/src/middleware/asyncHandler.js @@ -0,0 +1,10 @@ +module.exports = function asyncHandler(fn) { + return async function wrapped(req, res, next) { + try { + await fn(req, res, next); + } catch (error) { + next(error); + } + }; +}; + diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.js new file mode 100644 index 0000000..6e4e653 --- /dev/null +++ b/src/middleware/errorHandler.js @@ -0,0 +1,20 @@ +module.exports = function errorHandler(err, req, res, next) { + const status = err.status || 500; + const payload = { + success: false, + message: err.message || "Internal server error", + requestId: req.requestId + }; + + if (process.env.NODE_ENV !== "production") { + payload.stack = err.stack; + } + + if (status >= 500) { + // eslint-disable-next-line no-console + console.error(err); + } + + res.status(status).json(payload); +}; + diff --git a/src/middleware/requestContext.js b/src/middleware/requestContext.js new file mode 100644 index 0000000..c474649 --- /dev/null +++ b/src/middleware/requestContext.js @@ -0,0 +1,30 @@ +const { v4: uuidv4 } = require("uuid"); +const env = require("../config/env"); + +function requestContext(req, res, next) { + req.requestId = req.headers["x-request-id"] || uuidv4(); + res.setHeader("x-request-id", req.requestId); + next(); +} + +function requireWrapperApiKey(req, res, next) { + if (!env.WRAPPER_API_KEY) { + return next(); + } + + const apiKey = req.headers["x-api-key"]; + if (apiKey !== env.WRAPPER_API_KEY) { + return res.status(401).json({ + success: false, + message: "Invalid API key" + }); + } + + return next(); +} + +module.exports = { + requestContext, + requireWrapperApiKey +}; + diff --git a/src/modules/auth/auth.controller.js b/src/modules/auth/auth.controller.js new file mode 100644 index 0000000..bd77548 --- /dev/null +++ b/src/modules/auth/auth.controller.js @@ -0,0 +1,92 @@ +const { merchantRepository, uberConnectionRepository } = require("../../db/adapter"); +const { buildAuthorizeUrl, exchangeCodeForToken, refreshToken } = require("./auth.service"); + +function parseExpiresAt(expiresInSeconds) { + if (!expiresInSeconds) { + return null; + } + return new Date(Date.now() + Number(expiresInSeconds) * 1000).toISOString(); +} + +async function getAuthorizeUrl(req, res) { + const { merchantId, scope } = req.query; + if (!merchantId) { + return res.status(400).json({ success: false, message: "merchantId query param is required" }); + } + + const merchant = merchantRepository.findById(merchantId); + if (!merchant) { + return res.status(404).json({ success: false, message: "Merchant not found" }); + } + + const state = Buffer.from(JSON.stringify({ merchantId })).toString("base64url"); + const url = buildAuthorizeUrl({ state, scope }); + return res.json({ success: true, data: { url } }); +} + +async function oauthCallback(req, res) { + const { code, state } = req.query; + if (!code || !state) { + return res.status(400).json({ success: false, message: "code and state are required" }); + } + + const decodedState = JSON.parse(Buffer.from(state, "base64url").toString("utf8")); + const merchant = merchantRepository.findById(decodedState.merchantId); + if (!merchant) { + return res.status(404).json({ success: false, message: "Merchant not found from state" }); + } + + const tokenData = await exchangeCodeForToken(code); + + const connection = uberConnectionRepository.upsertByMerchantId(merchant.id, { + accessToken: tokenData.access_token, + refreshToken: tokenData.refresh_token, + tokenType: tokenData.token_type || "Bearer", + scope: tokenData.scope || null, + expiresAt: parseExpiresAt(tokenData.expires_in), + status: "active" + }); + + return res.json({ + success: true, + message: "Uber account connected successfully", + data: { + merchantId: merchant.id, + connectionId: connection.id + } + }); +} + +async function refreshMerchantToken(req, res) { + const { merchantId } = req.params; + const connection = uberConnectionRepository.findByMerchantId(merchantId); + if (!connection) { + return res.status(404).json({ success: false, message: "Uber connection not found for merchant" }); + } + if (!connection.refresh_token) { + return res.status(400).json({ success: false, message: "Refresh token not available for merchant" }); + } + + const tokenData = await refreshToken(connection.refresh_token); + const updated = uberConnectionRepository.upsertByMerchantId(merchantId, { + accessToken: tokenData.access_token, + refreshToken: tokenData.refresh_token || connection.refresh_token, + tokenType: tokenData.token_type || "Bearer", + scope: tokenData.scope || connection.scope, + expiresAt: parseExpiresAt(tokenData.expires_in), + status: "active" + }); + + return res.json({ + success: true, + message: "Token refreshed", + data: updated + }); +} + +module.exports = { + getAuthorizeUrl, + oauthCallback, + refreshMerchantToken +}; + diff --git a/src/modules/auth/auth.service.js b/src/modules/auth/auth.service.js new file mode 100644 index 0000000..a9ab972 --- /dev/null +++ b/src/modules/auth/auth.service.js @@ -0,0 +1,61 @@ +const axios = require("axios"); +const env = require("../../config/env"); + +const uberAuthClient = axios.create({ + baseURL: env.UBER_OAUTH_BASE_URL, + timeout: 20000 +}); + +function buildAuthorizeUrl({ state, scope = "eats.store eats.order" }) { + const params = new URLSearchParams({ + client_id: env.UBER_CLIENT_ID, + response_type: "code", + redirect_uri: env.UBER_REDIRECT_URI, + scope, + state + }); + + return `${env.UBER_OAUTH_BASE_URL}/oauth/v2/authorize?${params.toString()}`; +} + +async function exchangeCodeForToken(code) { + const payload = new URLSearchParams({ + client_id: env.UBER_CLIENT_ID, + client_secret: env.UBER_CLIENT_SECRET, + grant_type: "authorization_code", + redirect_uri: env.UBER_REDIRECT_URI, + code + }); + + const { data } = await uberAuthClient.post("/oauth/v2/token", payload.toString(), { + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }); + + return data; +} + +async function refreshToken(refreshToken) { + const payload = new URLSearchParams({ + client_id: env.UBER_CLIENT_ID, + client_secret: env.UBER_CLIENT_SECRET, + grant_type: "refresh_token", + refresh_token: refreshToken + }); + + const { data } = await uberAuthClient.post("/oauth/v2/token", payload.toString(), { + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }); + + return data; +} + +module.exports = { + buildAuthorizeUrl, + exchangeCodeForToken, + refreshToken +}; + diff --git a/src/modules/connections/connections.controller.js b/src/modules/connections/connections.controller.js new file mode 100644 index 0000000..f09f4d9 --- /dev/null +++ b/src/modules/connections/connections.controller.js @@ -0,0 +1,54 @@ +const { z } = require("zod"); +const { merchantRepository, uberConnectionRepository } = require("../../db/adapter"); + +const merchantSchema = z.object({ + id: z.string().optional(), + name: z.string().min(1), + externalRef: z.string().optional() +}); + +const manualConnectionSchema = z.object({ + merchantId: z.string().min(1), + uberUserId: z.string().optional(), + uberStoreId: z.string().optional(), + accessToken: z.string().min(1), + refreshToken: z.string().optional(), + tokenType: z.string().optional(), + scope: z.string().optional(), + expiresAt: z.string().datetime().optional() +}); + +async function upsertMerchant(req, res) { + const payload = merchantSchema.parse(req.body); + const merchant = merchantRepository.upsert(payload); + return res.status(201).json({ success: true, data: merchant }); +} + +async function listMerchants(req, res) { + const merchants = merchantRepository.list(); + return res.json({ success: true, data: merchants }); +} + +async function upsertManualConnection(req, res) { + const payload = manualConnectionSchema.parse(req.body); + const merchant = merchantRepository.findById(payload.merchantId); + if (!merchant) { + return res.status(404).json({ success: false, message: "Merchant not found" }); + } + + const connection = uberConnectionRepository.upsertByMerchantId(payload.merchantId, payload); + return res.status(201).json({ success: true, data: connection }); +} + +async function listConnections(req, res) { + const rows = uberConnectionRepository.list(); + return res.json({ success: true, data: rows }); +} + +module.exports = { + upsertMerchant, + listMerchants, + upsertManualConnection, + listConnections +}; + diff --git a/src/modules/health/health.controller.js b/src/modules/health/health.controller.js new file mode 100644 index 0000000..7e4b09a --- /dev/null +++ b/src/modules/health/health.controller.js @@ -0,0 +1,15 @@ +async function healthCheck(req, res) { + return res.json({ + success: true, + data: { + service: "uber-wrapper", + status: "ok", + timestamp: new Date().toISOString() + } + }); +} + +module.exports = { + healthCheck +}; + diff --git a/src/modules/proxy/proxy.controller.js b/src/modules/proxy/proxy.controller.js new file mode 100644 index 0000000..230d831 --- /dev/null +++ b/src/modules/proxy/proxy.controller.js @@ -0,0 +1,97 @@ +const { z } = require("zod"); +const proxyService = require("./proxy.service"); + +const genericSchema = z.object({ + merchantId: z.string().min(1), + method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"), + path: z.string().min(1), + query: z.record(z.string(), z.any()).optional(), + body: z.any().optional() +}); + +async function genericProxy(req, res) { + const payload = genericSchema.parse(req.body); + const data = await proxyService.genericProxy(payload); + return res.json({ success: true, data }); +} + +async function upsertMenu(req, res) { + const schema = z.object({ + merchantId: z.string().min(1), + storeId: z.string().min(1), + menu: z.any() + }); + const payload = schema.parse(req.body); + const data = await proxyService.menuUpsert({ + merchantId: payload.merchantId, + storeId: payload.storeId, + payload: payload.menu + }); + return res.json({ success: true, data }); +} + +async function getMenu(req, res) { + const schema = z.object({ + merchantId: z.string().min(1), + storeId: z.string().min(1) + }); + const payload = schema.parse(req.query); + const data = await proxyService.menuGet(payload); + return res.json({ success: true, data }); +} + +async function listOrders(req, res) { + const schema = z.object({ + merchantId: z.string().min(1), + storeId: z.string().min(1) + }); + const payload = schema.parse(req.query); + const data = await proxyService.ordersList({ + merchantId: payload.merchantId, + storeId: payload.storeId, + query: req.query + }); + return res.json({ success: true, data }); +} + +async function orderAction(req, res) { + const schema = z.object({ + merchantId: z.string().min(1), + action: z.enum(["accept", "deny", "ready", "cancel"]), + payload: z.any().optional() + }); + const parsed = schema.parse(req.body); + + const data = await proxyService.orderAction({ + merchantId: parsed.merchantId, + orderId: req.params.orderId, + action: parsed.action, + payload: parsed.payload + }); + return res.json({ success: true, data }); +} + +async function updateHours(req, res) { + const schema = z.object({ + merchantId: z.string().min(1), + storeId: z.string().min(1), + hours: z.any() + }); + const payload = schema.parse(req.body); + const data = await proxyService.updateStoreHours({ + merchantId: payload.merchantId, + storeId: payload.storeId, + payload: payload.hours + }); + return res.json({ success: true, data }); +} + +module.exports = { + genericProxy, + upsertMenu, + getMenu, + listOrders, + orderAction, + updateHours +}; + diff --git a/src/modules/proxy/proxy.service.js b/src/modules/proxy/proxy.service.js new file mode 100644 index 0000000..ea3d0b3 --- /dev/null +++ b/src/modules/proxy/proxy.service.js @@ -0,0 +1,161 @@ +const axios = require("axios"); +const env = require("../../config/env"); +const uberEndpoints = require("../../config/uberEndpoints"); +const { uberConnectionRepository, apiLogRepository } = require("../../db/adapter"); + +const uberApiClient = axios.create({ + baseURL: env.UBER_API_BASE_URL, + timeout: 30000 +}); + +function interpolatePath(pathTemplate, params = {}) { + let output = pathTemplate; + Object.entries(params).forEach(([key, value]) => { + output = output.replaceAll(`{${key}}`, encodeURIComponent(value)); + }); + return output; +} + +function buildAuthHeader(connection) { + return `${connection.token_type || "Bearer"} ${connection.access_token}`; +} + +async function callUberApi({ merchantId, method, uberPath, query, body, wrapperRoute }) { + const connection = uberConnectionRepository.findByMerchantId(merchantId); + if (!connection || connection.status !== "active") { + const error = new Error("Active Uber connection not found for merchant"); + error.status = 404; + throw error; + } + + try { + const response = await uberApiClient.request({ + method, + url: uberPath, + params: query, + data: body, + headers: { + Authorization: buildAuthHeader(connection), + "Content-Type": "application/json" + } + }); + + apiLogRepository.insert({ + merchantId, + method, + wrapperRoute, + uberPath, + responseStatus: response.status, + requestBody: body, + responseBody: response.data + }); + + return response.data; + } catch (error) { + const status = error.response?.status || 500; + const responseBody = error.response?.data || { message: error.message }; + + apiLogRepository.insert({ + merchantId, + method, + wrapperRoute, + uberPath, + responseStatus: status, + requestBody: body, + responseBody + }); + + const wrapped = new Error(`Uber API request failed: ${status}`); + wrapped.status = status; + wrapped.details = responseBody; + throw wrapped; + } +} + +async function genericProxy({ merchantId, method, path, query, body }) { + return callUberApi({ + merchantId, + method, + uberPath: path, + query, + body, + wrapperRoute: "/api/v1/uber/request" + }); +} + +async function menuUpsert({ merchantId, storeId, payload }) { + const uberPath = interpolatePath(uberEndpoints.menu.upsert, { storeId }); + return callUberApi({ + merchantId, + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/menu/upsert" + }); +} + +async function menuGet({ merchantId, storeId }) { + const uberPath = interpolatePath(uberEndpoints.menu.get, { storeId }); + return callUberApi({ + merchantId, + method: "GET", + uberPath, + wrapperRoute: "/api/v1/uber/menu" + }); +} + +async function ordersList({ merchantId, storeId, query }) { + const uberPath = interpolatePath(uberEndpoints.orders.list, { storeId }); + return callUberApi({ + merchantId, + method: "GET", + uberPath, + query, + wrapperRoute: "/api/v1/uber/orders" + }); +} + +async function orderAction({ merchantId, orderId, action, payload }) { + const routeMap = { + accept: uberEndpoints.orders.accept, + deny: uberEndpoints.orders.deny, + ready: uberEndpoints.orders.readyForPickup, + cancel: uberEndpoints.orders.cancel + }; + const template = routeMap[action]; + if (!template) { + const error = new Error("Unsupported order action"); + error.status = 400; + throw error; + } + const uberPath = interpolatePath(template, { orderId }); + + return callUberApi({ + merchantId, + method: "POST", + uberPath, + body: payload || {}, + wrapperRoute: "/api/v1/uber/orders/:orderId/action" + }); +} + +async function updateStoreHours({ merchantId, storeId, payload }) { + const uberPath = interpolatePath(uberEndpoints.stores.updateHours, { storeId }); + return callUberApi({ + merchantId, + method: "PUT", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/stores/hours" + }); +} + +module.exports = { + genericProxy, + menuUpsert, + menuGet, + ordersList, + orderAction, + updateStoreHours +}; + diff --git a/src/modules/webhooks/webhooks.controller.js b/src/modules/webhooks/webhooks.controller.js new file mode 100644 index 0000000..f508549 --- /dev/null +++ b/src/modules/webhooks/webhooks.controller.js @@ -0,0 +1,25 @@ +const { webhookRepository } = require("../../db/adapter"); + +async function handleUberWebhook(req, res) { + const merchantId = req.query.merchantId || req.body?.merchant_id || null; + const eventType = req.body?.event_type || req.body?.type || "unknown"; + + const row = webhookRepository.insert({ + provider: "uber", + merchantId, + eventType, + payloadJson: req.body, + headersJson: req.headers + }); + + return res.status(202).json({ + success: true, + message: "Webhook received", + data: { webhookEventId: row.id } + }); +} + +module.exports = { + handleUberWebhook +}; + diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js new file mode 100644 index 0000000..bf64ed5 --- /dev/null +++ b/src/routes/auth.routes.js @@ -0,0 +1,59 @@ +const express = require("express"); +const asyncHandler = require("../middleware/asyncHandler"); +const controller = require("../modules/auth/auth.controller"); + +const router = express.Router(); + +/** + * @openapi + * /api/v1/auth/uber/authorize-url: + * get: + * summary: Generate Uber OAuth authorize URL for merchant + * tags: + * - Auth + * parameters: + * - in: query + * name: merchantId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OAuth URL generated + */ +router.get("/uber/authorize-url", asyncHandler(controller.getAuthorizeUrl)); + +/** + * @openapi + * /api/v1/auth/uber/callback: + * get: + * summary: Uber OAuth callback endpoint + * tags: + * - Auth + * responses: + * 200: + * description: Uber account connected + */ +router.get("/uber/callback", asyncHandler(controller.oauthCallback)); + +/** + * @openapi + * /api/v1/auth/uber/{merchantId}/refresh-token: + * post: + * summary: Refresh Uber token for merchant + * tags: + * - Auth + * parameters: + * - in: path + * name: merchantId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Token refreshed + */ +router.post("/uber/:merchantId/refresh-token", asyncHandler(controller.refreshMerchantToken)); + +module.exports = router; + diff --git a/src/routes/connections.routes.js b/src/routes/connections.routes.js new file mode 100644 index 0000000..03420f1 --- /dev/null +++ b/src/routes/connections.routes.js @@ -0,0 +1,50 @@ +const express = require("express"); +const asyncHandler = require("../middleware/asyncHandler"); +const controller = require("../modules/connections/connections.controller"); + +const router = express.Router(); + +/** + * @openapi + * /api/v1/merchants: + * post: + * summary: Create or update merchant + * tags: + * - Merchants + * responses: + * 201: + * description: Merchant upserted + * get: + * summary: List merchants + * tags: + * - Merchants + * responses: + * 200: + * description: Merchant list + */ +router.post("/merchants", asyncHandler(controller.upsertMerchant)); +router.get("/merchants", asyncHandler(controller.listMerchants)); + +/** + * @openapi + * /api/v1/connections/uber: + * post: + * summary: Save manual Uber tokens for merchant + * tags: + * - Connections + * responses: + * 201: + * description: Connection stored + * get: + * summary: List Uber connections + * tags: + * - Connections + * responses: + * 200: + * description: Connection list + */ +router.post("/connections/uber", asyncHandler(controller.upsertManualConnection)); +router.get("/connections/uber", asyncHandler(controller.listConnections)); + +module.exports = router; + diff --git a/src/routes/health.routes.js b/src/routes/health.routes.js new file mode 100644 index 0000000..01227ae --- /dev/null +++ b/src/routes/health.routes.js @@ -0,0 +1,21 @@ +const express = require("express"); +const asyncHandler = require("../middleware/asyncHandler"); +const { healthCheck } = require("../modules/health/health.controller"); + +const router = express.Router(); + +/** + * @openapi + * /health: + * get: + * summary: Health check + * tags: + * - Health + * responses: + * 200: + * description: Service is healthy + */ +router.get("/", asyncHandler(healthCheck)); + +module.exports = router; + diff --git a/src/routes/proxy.routes.js b/src/routes/proxy.routes.js new file mode 100644 index 0000000..c1e7a6c --- /dev/null +++ b/src/routes/proxy.routes.js @@ -0,0 +1,92 @@ +const express = require("express"); +const asyncHandler = require("../middleware/asyncHandler"); +const controller = require("../modules/proxy/proxy.controller"); + +const router = express.Router(); + +/** + * @openapi + * /api/v1/uber/request: + * post: + * summary: Generic Uber passthrough for any Uber endpoint + * tags: + * - Uber Generic + * responses: + * 200: + * description: Uber response + */ +router.post("/uber/request", asyncHandler(controller.genericProxy)); + +/** + * @openapi + * /api/v1/uber/menu/upsert: + * post: + * summary: Upsert store menu + * tags: + * - Uber Menu + * responses: + * 200: + * description: Menu upserted + */ +router.post("/uber/menu/upsert", asyncHandler(controller.upsertMenu)); + +/** + * @openapi + * /api/v1/uber/menu: + * get: + * summary: Fetch store menu + * tags: + * - Uber Menu + * responses: + * 200: + * description: Menu fetched + */ +router.get("/uber/menu", asyncHandler(controller.getMenu)); + +/** + * @openapi + * /api/v1/uber/orders: + * get: + * summary: List store orders + * tags: + * - Uber Orders + * responses: + * 200: + * description: Orders fetched + */ +router.get("/uber/orders", asyncHandler(controller.listOrders)); + +/** + * @openapi + * /api/v1/uber/orders/{orderId}/action: + * post: + * summary: Trigger order action (accept, deny, ready, cancel) + * tags: + * - Uber Orders + * parameters: + * - in: path + * name: orderId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Order action sent + */ +router.post("/uber/orders/:orderId/action", asyncHandler(controller.orderAction)); + +/** + * @openapi + * /api/v1/uber/stores/hours: + * put: + * summary: Update store hours + * tags: + * - Uber Stores + * responses: + * 200: + * description: Store hours updated + */ +router.put("/uber/stores/hours", asyncHandler(controller.updateHours)); + +module.exports = router; + diff --git a/src/routes/webhooks.routes.js b/src/routes/webhooks.routes.js new file mode 100644 index 0000000..757692d --- /dev/null +++ b/src/routes/webhooks.routes.js @@ -0,0 +1,21 @@ +const express = require("express"); +const asyncHandler = require("../middleware/asyncHandler"); +const { handleUberWebhook } = require("../modules/webhooks/webhooks.controller"); + +const router = express.Router(); + +/** + * @openapi + * /api/v1/webhooks/uber: + * post: + * summary: Ingest Uber webhook events + * tags: + * - Webhooks + * responses: + * 202: + * description: Webhook accepted + */ +router.post("/webhooks/uber", asyncHandler(handleUberWebhook)); + +module.exports = router; + diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..4f21b9a --- /dev/null +++ b/src/server.js @@ -0,0 +1,11 @@ +const app = require("./app"); +const env = require("./config/env"); +const { initSchema } = require("./db/sqlite"); + +initSchema(); + +app.listen(env.PORT, () => { + // eslint-disable-next-line no-console + console.log(`Uber Wrapper listening on http://localhost:${env.PORT}`); +}); +