diff --git a/hotel-booking-backend/.env.example b/hotel-booking-backend/.env.example
deleted file mode 100644
index 1b3dab7..0000000
--- a/hotel-booking-backend/.env.example
+++ /dev/null
@@ -1,58 +0,0 @@
-# ================================================
-# Hotel Booking Backend - Environment Variables
-# ================================================
-# Copy this file to .env and fill in your values
-
-# ================================================
-# SERVER CONFIGURATION
-# ================================================
-# Port number (Render will set this automatically in production)
-PORT=7002
-
-# Node environment (development, production, test)
-NODE_ENV=development
-
-# ================================================
-# DATABASE CONFIGURATION
-# ================================================
-# MongoDB connection string (MongoDB Atlas or local)
-# Format: mongodb+srv://username:password@cluster.mongodb.net/database?retryWrites=true&w=majority
-MONGODB_CONNECTION_STRING=your_mongodb_connection_string_here
-
-# ================================================
-# AUTHENTICATION & SECURITY
-# ================================================
-# JWT Secret Key (use a strong, random string)
-# Generate with: openssl rand -base64 512
-JWT_SECRET_KEY=your_jwt_secret_key_here
-
-# ================================================
-# FRONTEND CONFIGURATION
-# ================================================
-# Frontend URL for CORS (your React app URL)
-# Local development: http://localhost:5174
-# Production: https://your-frontend.netlify.app
-FRONTEND_URL=http://localhost:5174
-
-# ================================================
-# CLOUDINARY CONFIGURATION
-# ================================================
-# Cloudinary credentials for image storage
-# Sign up at: https://cloudinary.com
-CLOUDINARY_CLOUD_NAME=your_cloud_name
-CLOUDINARY_API_KEY=your_api_key
-CLOUDINARY_API_SECRET=your_api_secret
-
-# ================================================
-# STRIPE PAYMENT CONFIGURATION
-# ================================================
-# Stripe API key for payment processing
-# Get from: https://dashboard.stripe.com/apikeys
-# Use test key for development: sk_test_...
-# Use live key for production: sk_live_...
-STRIPE_API_KEY=your_stripe_secret_key_here
-
-# ================================================
-# OPTIONAL CONFIGURATION
-# ================================================
-# Add any additional environment variables below
diff --git a/hotel-booking-backend/.gitignore b/hotel-booking-backend/.gitignore
index 747328a..c72de48 100644
--- a/hotel-booking-backend/.gitignore
+++ b/hotel-booking-backend/.gitignore
@@ -27,3 +27,8 @@ dist-ssr
.env.e2e
node_modules
.cursorignore
+
+
+firebase.ts
+
+serviceAccountKey.json
\ No newline at end of file
diff --git a/hotel-booking-backend/README.md b/hotel-booking-backend/README.md
index a5e652a..0c5a3da 100644
--- a/hotel-booking-backend/README.md
+++ b/hotel-booking-backend/README.md
@@ -277,7 +277,7 @@ const verifyToken = (req: Request, res: Response, next: NextFunction) => {
```typescript
// Login endpoint returns both cookie and token
-res.cookie("auth_token", token, {
+res.cookie("session_id", token, {
httpOnly: true,
secure: true,
sameSite: "none",
diff --git a/hotel-booking-backend/package-lock.json b/hotel-booking-backend/package-lock.json
index 0f0ae49..a83f355 100644
--- a/hotel-booking-backend/package-lock.json
+++ b/hotel-booking-backend/package-lock.json
@@ -9,8 +9,15 @@
"version": "0.2.0",
"license": "ISC",
"dependencies": {
+ "@types/bcryptjs": "^2.4.6",
"@types/compression": "^1.8.1",
+ "@types/cookie-parser": "^1.4.6",
+ "@types/cors": "^2.8.16",
+ "@types/express": "^4.17.21",
+ "@types/jsonwebtoken": "^9.0.5",
"@types/morgan": "^1.9.10",
+ "@types/multer": "^1.4.11",
+ "@types/node": "^20.9.0",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"bcryptjs": "^2.4.3",
@@ -23,27 +30,23 @@
"express": "^4.18.2",
"express-rate-limit": "^8.0.1",
"express-validator": "^7.0.1",
+ "firebase-admin": "^13.6.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0",
"mongoose": "^8.0.0",
"morgan": "^1.10.1",
"multer": "^1.4.5-lts.1",
+ "nodemon": "^3.0.1",
"stripe": "^14.8.0",
"swagger-jsdoc": "^6.2.8",
- "swagger-ui-express": "^5.0.1"
+ "swagger-ui-express": "^5.0.1",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.2.2",
+ "uuid": "^13.0.0"
},
"devDependencies": {
- "@types/bcryptjs": "^2.4.6",
- "@types/cookie-parser": "^1.4.6",
- "@types/cors": "^2.8.16",
- "@types/express": "^4.17.21",
- "@types/jsonwebtoken": "^9.0.5",
- "@types/multer": "^1.4.11",
- "@types/node": "^20.9.0",
- "nodemon": "^3.0.1",
- "ts-node": "^10.9.1",
- "typescript": "^5.2.2"
+ "@types/uuid": "^10.0.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
@@ -94,7 +97,6 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -102,11 +104,270 @@
"node": ">=12"
}
},
+ "node_modules/@fastify/busboy": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz",
+ "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==",
+ "license": "MIT"
+ },
+ "node_modules/@firebase/app-check-interop-types": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
+ "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app-types": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
+ "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/auth-interop-types": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
+ "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/component": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz",
+ "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/util": "1.13.0",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz",
+ "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.3",
+ "@firebase/auth-interop-types": "0.2.4",
+ "@firebase/component": "0.7.0",
+ "@firebase/logger": "0.5.0",
+ "@firebase/util": "1.13.0",
+ "faye-websocket": "0.11.4",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-compat": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz",
+ "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.0",
+ "@firebase/database": "1.1.0",
+ "@firebase/database-types": "1.0.16",
+ "@firebase/logger": "0.5.0",
+ "@firebase/util": "1.13.0",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-types": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz",
+ "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-types": "0.9.3",
+ "@firebase/util": "1.13.0"
+ }
+ },
+ "node_modules/@firebase/logger": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
+ "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/util": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz",
+ "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@google-cloud/firestore": {
+ "version": "7.11.6",
+ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz",
+ "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@opentelemetry/api": "^1.3.0",
+ "fast-deep-equal": "^3.1.1",
+ "functional-red-black-tree": "^1.0.1",
+ "google-gax": "^4.3.3",
+ "protobufjs": "^7.2.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/paginator": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
+ "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "arrify": "^2.0.0",
+ "extend": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/projectify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
+ "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/promisify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
+ "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@google-cloud/storage": {
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.3.tgz",
+ "integrity": "sha512-gOnCAbFgAYKRozywLsxagdevTF7Gm+2Ncz5u5CQAuOv/2VCa0rdGJWvJFDOftPx1tc+q8TXiC2pEJfFKu+yeMQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@google-cloud/paginator": "^5.0.0",
+ "@google-cloud/projectify": "^4.0.0",
+ "@google-cloud/promisify": "<4.1.0",
+ "abort-controller": "^3.0.0",
+ "async-retry": "^1.3.3",
+ "duplexify": "^4.1.3",
+ "fast-xml-parser": "^4.4.1",
+ "gaxios": "^6.0.2",
+ "google-auth-library": "^9.6.3",
+ "html-entities": "^2.5.2",
+ "mime": "^3.0.0",
+ "p-limit": "^3.0.1",
+ "retry-request": "^7.0.0",
+ "teeny-request": "^9.0.0",
+ "uuid": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@google-cloud/storage/node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@google-cloud/storage/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz",
+ "integrity": "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/proto-loader": "^0.8.0",
+ "@js-sdsl/ordered-map": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=12.10.0"
+ }
+ },
+ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
+ "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.5.3",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.15",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
- "dev": true,
"engines": {
"node": ">=6.0.0"
}
@@ -114,19 +375,28 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
+ "node_modules/@js-sdsl/ordered-map": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
@@ -142,6 +412,90 @@
"sparse-bitfield": "^3.0.3"
}
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
@@ -149,35 +503,40 @@
"hasInstallScript": true,
"license": "Apache-2.0"
},
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
- "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
- "dev": true
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
- "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
- "dev": true
+ "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ=="
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
@@ -188,6 +547,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/caseless": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
+ "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
@@ -210,7 +576,6 @@
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==",
- "dev": true,
"dependencies": {
"@types/express": "*"
}
@@ -219,7 +584,6 @@
"version": "2.8.16",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.16.tgz",
"integrity": "sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg==",
- "dev": true,
"dependencies": {
"@types/node": "*"
}
@@ -261,11 +625,17 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz",
"integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==",
- "dev": true,
"dependencies": {
"@types/node": "*"
}
},
+ "node_modules/@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -284,7 +654,6 @@
"version": "1.4.11",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz",
"integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==",
- "dev": true,
"dependencies": {
"@types/express": "*"
}
@@ -307,6 +676,19 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
+ "node_modules/@types/request": {
+ "version": "2.48.13",
+ "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz",
+ "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/caseless": "*",
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "form-data": "^2.5.5"
+ }
+ },
"node_modules/@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
@@ -342,6 +724,20 @@
"@types/serve-static": "*"
}
},
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -360,8 +756,20 @@
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
},
"node_modules/accepts": {
"version": "1.3.8",
@@ -379,7 +787,6 @@
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
- "dev": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -391,16 +798,49 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz",
"integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==",
- "dev": true,
"engines": {
"node": ">=0.4.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"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,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -417,8 +857,7 @@
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
"node_modules/argparse": {
"version": "2.0.1",
@@ -431,14 +870,61 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
- "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=="
- },
- "node_modules/basic-auth": {
+ "node_modules/arrify": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async-retry": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
+ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "retry": "0.13.1"
+ }
+ },
+ "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",
+ "optional": true
+ },
+ "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=="
+ },
+ "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": {
@@ -459,11 +945,19 @@
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
},
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -506,7 +1000,6 @@
"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"
@@ -593,7 +1086,6 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
- "dev": true,
"funding": [
{
"type": "individual",
@@ -616,6 +1108,21 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/cloudinary": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.41.0.tgz",
@@ -638,6 +1145,39 @@
"lodash": ">=4.0"
}
},
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "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",
+ "optional": true,
+ "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",
@@ -782,8 +1322,7 @@
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "dev": true
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
"node_modules/cross-env": {
"version": "7.0.3",
@@ -825,6 +1364,16 @@
"ms": "2.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",
+ "optional": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -848,7 +1397,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
"engines": {
"node": ">=0.3.1"
}
@@ -890,6 +1438,34 @@
"node": ">= 0.4"
}
},
+ "node_modules/duplexify": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
+ "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "end-of-stream": "^1.4.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1",
+ "stream-shift": "^1.0.2"
+ }
+ },
+ "node_modules/duplexify/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",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -904,6 +1480,13 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -913,6 +1496,16 @@
"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",
+ "optional": true,
+ "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",
@@ -943,6 +1536,32 @@
"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",
+ "optional": true,
+ "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/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -967,6 +1586,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
@@ -1052,11 +1681,62 @@
"node": ">= 0.6"
}
},
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/farmhash-modern": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz",
+ "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-xml-parser": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
+ "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "strnum": "^1.1.1"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -1083,6 +1763,78 @@
"node": ">= 0.8"
}
},
+ "node_modules/firebase-admin": {
+ "version": "13.6.0",
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.6.0.tgz",
+ "integrity": "sha512-GdPA/t0+Cq8p1JnjFRBmxRxAGvF/kl2yfdhALl38PrRp325YxyQ5aNaHui0XmaKcKiGRFIJ/EgBNWFoDP0onjw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@fastify/busboy": "^3.0.0",
+ "@firebase/database-compat": "^2.0.0",
+ "@firebase/database-types": "^1.0.6",
+ "@types/node": "^22.8.7",
+ "farmhash-modern": "^1.1.0",
+ "fast-deep-equal": "^3.1.1",
+ "google-auth-library": "^9.14.2",
+ "jsonwebtoken": "^9.0.0",
+ "jwks-rsa": "^3.1.0",
+ "node-forge": "^1.3.1",
+ "uuid": "^11.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@google-cloud/firestore": "^7.11.0",
+ "@google-cloud/storage": "^7.14.0"
+ }
+ },
+ "node_modules/firebase-admin/node_modules/@types/node": {
+ "version": "22.19.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
+ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/firebase-admin/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/firebase-admin/node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
+ "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.35",
+ "safe-buffer": "^5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1110,7 +1862,6 @@
"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,
"optional": true,
"os": [
@@ -1129,6 +1880,140 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/gaxios": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "is-stream": "^2.0.0",
+ "node-fetch": "^2.6.9",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gaxios/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
+ "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "gaxios": "^5.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/gcp-metadata/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/gcp-metadata/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/gcp-metadata/node_modules/gaxios": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
+ "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^5.0.0",
+ "is-stream": "^2.0.0",
+ "node-fetch": "^2.6.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/gcp-metadata/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gcp-metadata/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",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -1191,7 +2076,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -1199,6 +2083,105 @@
"node": ">= 6"
}
},
+ "node_modules/google-auth-library": {
+ "version": "9.15.1",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^6.1.1",
+ "gcp-metadata": "^6.1.0",
+ "gtoken": "^7.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-auth-library/node_modules/gcp-metadata": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^6.1.1",
+ "google-logging-utils": "^0.0.2",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-auth-library/node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/google-auth-library/node_modules/jws": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/google-gax": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz",
+ "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/grpc-js": "^1.10.9",
+ "@grpc/proto-loader": "^0.7.13",
+ "@types/long": "^4.0.0",
+ "abort-controller": "^3.0.0",
+ "duplexify": "^4.0.0",
+ "google-auth-library": "^9.3.0",
+ "node-fetch": "^2.7.0",
+ "object-hash": "^3.0.0",
+ "proto3-json-serializer": "^2.0.2",
+ "protobufjs": "^7.3.2",
+ "retry-request": "^7.0.0",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-gax/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1211,11 +2194,44 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gtoken": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
+ "license": "MIT",
+ "dependencies": {
+ "gaxios": "^6.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/gtoken/node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/gtoken/node_modules/jws": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"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,
"engines": {
"node": ">=4"
}
@@ -1232,6 +2248,22 @@
"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",
+ "optional": true,
+ "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",
@@ -1253,6 +2285,23 @@
"node": ">=18.0.0"
}
},
+ "node_modules/html-entities": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -1269,6 +2318,101 @@
"node": ">= 0.8"
}
},
+ "node_modules/http-parser-js": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
+ "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
+ "license": "MIT"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/http-proxy-agent/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",
+ "optional": true
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent/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/https-proxy-agent/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/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -1284,8 +2428,7 @@
"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
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
},
"node_modules/inflight": {
"version": "1.0.6",
@@ -1324,7 +2467,6 @@
"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,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -1336,16 +2478,24 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"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,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -1357,12 +2507,23 @@
"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-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -1373,6 +2534,15 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -1385,6 +2555,15 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -1421,6 +2600,46 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/jwks-rsa": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz",
+ "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "^4.17.20",
+ "@types/jsonwebtoken": "^9.0.4",
+ "debug": "^4.3.4",
+ "jose": "^4.15.4",
+ "limiter": "^1.1.5",
+ "lru-memoizer": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/jwks-rsa/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/jwks-rsa/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/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
@@ -1439,11 +2658,29 @@
"node": ">=12.0.0"
}
},
+ "node_modules/limiter": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
+ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -1499,6 +2736,13 @@
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0",
+ "optional": true
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -1510,11 +2754,20 @@
"node": ">=10"
}
},
+ "node_modules/lru-memoizer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
+ "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.clonedeep": "^4.5.0",
+ "lru-cache": "6.0.0"
+ }
+ },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
@@ -1799,11 +3052,61 @@
"node": ">= 0.6"
}
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-fetch/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/node-fetch/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/node-fetch/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "license": "(BSD-3-Clause OR GPL-2.0)",
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
"node_modules/nodemon": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
"integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==",
- "dev": true,
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
@@ -1831,7 +3134,6 @@
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
"dependencies": {
"ms": "^2.1.1"
}
@@ -1839,14 +3141,12 @@
"node_modules/nodemon/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
- "dev": true,
"dependencies": {
"abbrev": "1"
},
@@ -1861,7 +3161,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1874,6 +3173,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -1923,6 +3232,22 @@
"license": "MIT",
"peer": true
},
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1959,7 +3284,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -1972,6 +3296,44 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
+ "node_modules/proto3-json-serializer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
+ "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "protobufjs": "^7.2.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -1987,8 +3349,7 @@
"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
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -2070,7 +3431,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -2078,6 +3438,41 @@
"node": ">=8.10.0"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/retry-request": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
+ "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/request": "^2.48.8",
+ "extend": "^3.0.2",
+ "teeny-request": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2278,7 +3673,6 @@
"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,
"dependencies": {
"semver": "^7.5.3"
},
@@ -2304,6 +3698,23 @@
"node": ">= 0.8"
}
},
+ "node_modules/stream-events": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
+ "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "stubs": "^3.0.0"
+ }
+ },
+ "node_modules/stream-shift": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
+ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -2325,6 +3736,34 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/stripe": {
"version": "14.8.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.8.0.tgz",
@@ -2337,11 +3776,30 @@
"node": ">=12.*"
}
},
+ "node_modules/strnum": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/stubs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
+ "license": "MIT",
+ "optional": true
+ },
"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,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -2405,11 +3863,93 @@
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
+ "node_modules/teeny-request": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
+ "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "node-fetch": "^2.6.9",
+ "stream-events": "^1.0.5",
+ "uuid": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/teeny-request/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/teeny-request/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/teeny-request/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/teeny-request/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",
+ "optional": true
+ },
+ "node_modules/teeny-request/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"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"
@@ -2431,7 +3971,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
- "dev": true,
"dependencies": {
"nopt": "~1.0.10"
},
@@ -2455,7 +3994,6 @@
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
- "dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -2494,6 +4032,12 @@
}
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -2515,7 +4059,6 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
- "dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -2527,8 +4070,7 @@
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
- "dev": true
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
},
"node_modules/undici-types": {
"version": "5.26.5",
@@ -2557,11 +4099,23 @@
"node": ">= 0.4.0"
}
},
+ "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/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
"node_modules/validator": {
"version": "13.11.0",
@@ -2588,6 +4142,29 @@
"node": ">=12"
}
},
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
@@ -2615,6 +4192,24 @@
"node": ">= 8"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -2629,6 +4224,16 @@
"node": ">=0.4"
}
},
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -2643,15 +4248,56 @@
"node": ">= 6"
}
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
"engines": {
"node": ">=6"
}
},
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/z-schema": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
diff --git a/hotel-booking-backend/package.json b/hotel-booking-backend/package.json
index 4062879..975cec2 100644
--- a/hotel-booking-backend/package.json
+++ b/hotel-booking-backend/package.json
@@ -12,17 +12,17 @@
"author": "Arnob Mahmud",
"license": "ISC",
"dependencies": {
- "@types/compression": "^1.8.1",
- "@types/morgan": "^1.9.10",
- "@types/swagger-jsdoc": "^6.0.4",
- "@types/swagger-ui-express": "^4.1.8",
"@types/bcryptjs": "^2.4.6",
+ "@types/compression": "^1.8.1",
"@types/cookie-parser": "^1.4.6",
"@types/cors": "^2.8.16",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5",
+ "@types/morgan": "^1.9.10",
"@types/multer": "^1.4.11",
"@types/node": "^20.9.0",
+ "@types/swagger-jsdoc": "^6.0.4",
+ "@types/swagger-ui-express": "^4.1.8",
"bcryptjs": "^2.4.3",
"cloudinary": "^1.41.0",
"compression": "^1.8.1",
@@ -33,6 +33,7 @@
"express": "^4.18.2",
"express-rate-limit": "^8.0.1",
"express-validator": "^7.0.1",
+ "firebase-admin": "^13.6.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0",
@@ -44,6 +45,10 @@
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"ts-node": "^10.9.1",
- "typescript": "^5.2.2"
+ "typescript": "^5.2.2",
+ "uuid": "^13.0.0"
+ },
+ "devDependencies": {
+ "@types/uuid": "^10.0.0"
}
}
diff --git a/hotel-booking-backend/src/index.ts b/hotel-booking-backend/src/index.ts
index 4059eb5..1c37ffb 100644
--- a/hotel-booking-backend/src/index.ts
+++ b/hotel-booking-backend/src/index.ts
@@ -5,13 +5,15 @@ import mongoose from "mongoose";
import userRoutes from "./routes/users";
import authRoutes from "./routes/auth";
import cookieParser from "cookie-parser";
-import path from "path";
import { v2 as cloudinary } from "cloudinary";
import myHotelRoutes from "./routes/my-hotels";
import hotelRoutes from "./routes/hotels";
import bookingRoutes from "./routes/my-bookings";
import bookingsManagementRoutes from "./routes/bookings";
+import reviewRoutes from "./routes/reviews";
+import favoriteRoutes from "./routes/favorites";
import healthRoutes from "./routes/health";
+import adminRoutes from "./routes/admin";
import businessInsightsRoutes from "./routes/business-insights";
import swaggerUi from "swagger-ui-express";
import { specs } from "./swagger";
@@ -27,20 +29,20 @@ const requiredEnvVars = [
"CLOUDINARY_CLOUD_NAME",
"CLOUDINARY_API_KEY",
"CLOUDINARY_API_SECRET",
- "STRIPE_API_KEY",
+ "STRIPE_SECRET_KEY",
];
const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]);
if (missingEnvVars.length > 0) {
- console.error("❌ Missing required environment variables:");
+ console.error("Missing required environment variables:");
missingEnvVars.forEach((envVar) => console.error(` - ${envVar}`));
process.exit(1);
}
-console.log("✅ All required environment variables are present");
-console.log(`🌍 Environment: ${process.env.NODE_ENV || "development"}`);
-console.log(`🔗 Frontend URL: ${process.env.FRONTEND_URL || "Not set"}`);
+console.log("All required environment variables are present");
+console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
+console.log(`Frontend URL: ${process.env.FRONTEND_URL || "Not set"}`);
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
@@ -48,79 +50,44 @@ cloudinary.config({
api_secret: process.env.CLOUDINARY_API_SECRET,
});
-console.log("☁️ Cloudinary configured successfully");
-
// MongoDB Connection with Error Handling
const connectDB = async () => {
try {
- console.log("📡 Attempting to connect to MongoDB...");
+ console.log("Attempting to connect to MongoDB...");
await mongoose.connect(process.env.MONGODB_CONNECTION_STRING as string);
- console.log("✅ MongoDB connected successfully");
- console.log(`📦 Database: ${mongoose.connection.db.databaseName}`);
+ console.log("MongoDB connected successfully");
+ console.log(`Database: ${mongoose.connection.db.databaseName}`);
} catch (error) {
- console.error("❌ MongoDB connection error:", error);
- console.error("💡 Please check your MONGODB_CONNECTION_STRING");
+ console.error("MongoDB connection error:", error);
+ console.error("Please check your MONGODB_CONNECTION_STRING");
process.exit(1);
}
};
// Handle MongoDB connection events
mongoose.connection.on("disconnected", () => {
- console.warn("⚠️ MongoDB disconnected. Attempting to reconnect...");
+ console.warn("MongoDB disconnected. Attempting to reconnect...");
});
mongoose.connection.on("error", (error) => {
- console.error("❌ MongoDB connection error:", error);
+ console.error("MongoDB connection error:", error);
});
mongoose.connection.on("reconnected", () => {
- console.log("✅ MongoDB reconnected successfully");
+ console.log("MongoDB reconnected successfully");
});
connectDB();
const app = express();
-// Security middleware
-app.use(helmet());
-
-// Trust proxy for production (fixes rate limiting issues)
-app.set("trust proxy", 1);
-
-// Rate limiting - more lenient for payment endpoints
-const generalLimiter = rateLimit({
- windowMs: 15 * 60 * 1000, // 15 minutes
- max: 200, // Increased limit for general requests
- message: "Too many requests from this IP, please try again later.",
- standardHeaders: true,
- legacyHeaders: false,
-});
-
-// Special limiter for payment endpoints
-const paymentLimiter = rateLimit({
- windowMs: 15 * 60 * 1000, // 15 minutes
- max: 50, // Higher limit for payment requests
- message: "Too many payment requests, please try again later.",
- standardHeaders: true,
- legacyHeaders: false,
-});
-
-app.use("/api/", generalLimiter);
-app.use("/api/hotels/*/bookings/payment-intent", paymentLimiter);
-
-// Compression middleware
-app.use(compression());
-
-// Logging middleware
-app.use(morgan("combined"));
-
const allowedOrigins = [
process.env.FRONTEND_URL,
+ "https://98.86.196.119.nip.io",
"http://localhost:5174",
"http://localhost:5173",
- "https://mern-booking-hotel.netlify.app",
- "https://mern-booking-hotel.netlify.app/",
].filter((origin): origin is string => Boolean(origin));
+
app.use(
cors({
origin: (origin, callback) => {
@@ -128,6 +95,7 @@ app.use(
if (!origin) return callback(null, true);
// Allow all Netlify preview URLs
+ // Izbaci netlify
if (origin.includes("netlify.app")) {
return callback(null, true);
}
@@ -184,10 +152,47 @@ app.options(
],
})
);
+
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
+// Security middleware
+app.use(helmet());
+
+// Trust proxy for production (fixes rate limiting issues)
+app.set("trust proxy", 1);
+
+// Rate limiting - more lenient for payment endpoints
+const generalLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000, // 15 minutes
+ max: 200, // Increased limit for general requests
+ message: "Too many requests from this IP, please try again later.",
+ standardHeaders: true,
+ legacyHeaders: false,
+});
+
+// Special limiter for payment endpoints
+const paymentLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000, // 15 minutes
+ max: 50, // Higher limit for payment requests
+ message: "Too many payment requests, please try again later.",
+ standardHeaders: true,
+ legacyHeaders: false,
+});
+
+app.use("/api/", generalLimiter);
+app.use("/api/hotels/*/bookings/payment-intent", paymentLimiter);
+app.use("/api/reviews", reviewRoutes);
+app.use("/api/favorites", favoriteRoutes);
+app.use("/api/admin", adminRoutes);
+
+// Compression middleware
+app.use(compression());
+
+// Logging middleware
+app.use(morgan("combined"));
+
app.use((req, res, next) => {
// Ensure Vary header for CORS
res.header("Vary", "Origin");
diff --git a/hotel-booking-backend/src/middleware/auth.ts b/hotel-booking-backend/src/middleware/auth.ts
index b5ce139..af1aa20 100644
--- a/hotel-booking-backend/src/middleware/auth.ts
+++ b/hotel-booking-backend/src/middleware/auth.ts
@@ -1,37 +1,40 @@
-import { NextFunction, Request, Response } from "express";
-import jwt, { JwtPayload } from "jsonwebtoken";
+import { Request, Response, NextFunction } from "express";
+import jwt from "jsonwebtoken";
declare global {
namespace Express {
interface Request {
userId: string;
+ userRole: string;
}
}
}
const verifyToken = (req: Request, res: Response, next: NextFunction) => {
- // Check for token in Authorization header first (for axios interceptor)
- const authHeader = req.headers.authorization;
- let token: string | undefined;
-
- if (authHeader && authHeader.startsWith("Bearer ")) {
- token = authHeader.substring(7);
- } else {
- // Fallback to session cookie
- token = req.cookies["session_id"];
- }
+ // 1. Preuzimanje tokena iz kolačića (HttpOnly)
+ const token = req.cookies["auth_token"];
if (!token) {
- return res.status(401).json({ message: "unauthorized" });
+ // Ako nema tokena u kolačiću, korisnik je neautentifikovan
+ return res.status(401).json({ message: "Unauthorized: No token provided" });
}
try {
- const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY as string);
- req.userId = (decoded as JwtPayload).userId;
+ const decoded = jwt.verify(
+ token,
+ process.env.JWT_SECRET_KEY as string
+ ) as { userId: string, userRole: string };
+
+ req.userId = decoded.userId;
+ req.userRole = decoded.userRole;
+
next();
} catch (error) {
- return res.status(401).json({ message: "unauthorized" });
+ console.error("Token verification failed:", error);
+ res.clearCookie("auth_token", { path: "/" });
+
+ return res.status(401).json({ message: "Unauthorized: Invalid or expired token" });
}
};
-export default verifyToken;
+export default verifyToken;
\ No newline at end of file
diff --git a/hotel-booking-backend/src/middleware/rateLimiter.ts b/hotel-booking-backend/src/middleware/rateLimiter.ts
new file mode 100644
index 0000000..5a778cb
--- /dev/null
+++ b/hotel-booking-backend/src/middleware/rateLimiter.ts
@@ -0,0 +1,25 @@
+// src/middleware/rateLimiter.ts
+
+import rateLimit from "express-rate-limit";
+
+// Ograničenje za osjetljive rute poput login/registracije
+// 5 pokušaja unutar 15 minuta po IP adresi
+export const authLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000, // 15 minuta
+ max: 10, // Maksimalno 10 zahtjeva po IP adresi
+ message: JSON.stringify({
+ message: "Too many requests, please try again after 15 minutes",
+ status: 429,
+ }),
+ standardHeaders: true,
+ legacyHeaders: false,
+});
+
+// Ograničenje za opće javne rute (ako ih ima)
+export const apiLimiter = rateLimit({
+ windowMs: 60 * 60 * 1000, // 60 minuta
+ max: 100, // Maksimalno 100 zahtjeva po IP adresi
+ message: "Too many requests created from this IP, please try again after an hour",
+ standardHeaders: true,
+ legacyHeaders: false,
+});
\ No newline at end of file
diff --git a/hotel-booking-backend/src/middleware/requireRole.ts b/hotel-booking-backend/src/middleware/requireRole.ts
new file mode 100644
index 0000000..1aa5c6b
--- /dev/null
+++ b/hotel-booking-backend/src/middleware/requireRole.ts
@@ -0,0 +1,15 @@
+import { Request, Response, NextFunction } from "express";
+
+export function requireRole(...allowedRoles: string[]) {
+ return (req: any, res: Response, next: NextFunction) => {
+ if (!req.userRole) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ if (!allowedRoles.includes(req.userRole)) {
+ return res.status(403).json({ message: "Forbidden" });
+ }
+
+ next();
+ };
+}
diff --git a/hotel-booking-backend/src/models/favorite.ts b/hotel-booking-backend/src/models/favorite.ts
new file mode 100644
index 0000000..6158995
--- /dev/null
+++ b/hotel-booking-backend/src/models/favorite.ts
@@ -0,0 +1,20 @@
+import mongoose, { Document } from "mongoose";
+
+export interface IFavorite extends Document {
+ userId: string;
+ hotelId: string;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const favoriteSchema = new mongoose.Schema(
+ {
+ userId: { type: String, required: true, index: true },
+ hotelId: { type: String, required: true, index: true },
+ },
+ { timestamps: true }
+);
+
+favoriteSchema.index({ userId: 1, hotelId: 1 }, { unique: true });
+
+export default mongoose.model("Favorite", favoriteSchema);
diff --git a/hotel-booking-backend/src/models/review.ts b/hotel-booking-backend/src/models/review.ts
index adbe9aa..a87a0c0 100644
--- a/hotel-booking-backend/src/models/review.ts
+++ b/hotel-booking-backend/src/models/review.ts
@@ -4,7 +4,7 @@ export interface IReview extends Document {
_id: string;
userId: string;
hotelId: string;
- bookingId: string;
+ bookingId?: string;
rating: number;
comment: string;
categories: {
@@ -24,7 +24,7 @@ const reviewSchema = new mongoose.Schema(
{
userId: { type: String, required: true, index: true },
hotelId: { type: String, required: true, index: true },
- bookingId: { type: String, required: true, index: true },
+ bookingId: { type: String, index: true },
rating: { type: Number, required: true, min: 1, max: 5, index: true },
comment: { type: String, required: true },
categories: {
diff --git a/hotel-booking-backend/src/models/user.ts b/hotel-booking-backend/src/models/user.ts
index 29bc13e..8ff982c 100644
--- a/hotel-booking-backend/src/models/user.ts
+++ b/hotel-booking-backend/src/models/user.ts
@@ -8,7 +8,6 @@ const userSchema = new mongoose.Schema(
password: { type: String, required: true },
firstName: { type: String, required: true },
lastName: { type: String, required: true },
- // New fields for better user management
role: {
type: String,
enum: ["user", "admin", "hotel_owner"],
diff --git a/hotel-booking-backend/src/routes/admin.ts b/hotel-booking-backend/src/routes/admin.ts
new file mode 100644
index 0000000..9904f85
--- /dev/null
+++ b/hotel-booking-backend/src/routes/admin.ts
@@ -0,0 +1,208 @@
+import express, { Request, Response } from "express";
+import verifyToken from "../middleware/auth";
+import { requireRole } from "../middleware/requireRole";
+
+import User from "../models/user";
+import Hotel from "../models/hotel";
+import Booking from "../models/booking";
+import Favorite from "../models/favorite";
+
+const router = express.Router();
+
+/**
+ * @swagger
+ * /api/admin/dashboard:
+ * get:
+ * summary: Admin dashboard overview (KPIs + charts)
+ * tags: [Admin]
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: Dashboard data
+ * 401:
+ * description: Unauthorized
+ * 403:
+ * description: Forbidden
+ */
+router.get(
+ "/dashboard",
+ verifyToken,
+ requireRole("admin"),
+ async (req: Request, res: Response) => {
+ try {
+ const now = new Date();
+
+ const [totalUsers, totalHotels, totalFavorites] = await Promise.all([
+ User.countDocuments({}),
+ Hotel.countDocuments({}),
+ Favorite.countDocuments({}),
+ ]);
+
+ // Aktivne rezervacije
+ const activeBookings = await Booking.countDocuments({
+ checkOut: { $gte: now },
+ status: { $in: ["pending", "confirmed"] },
+ });
+
+ // Ukupan prihod
+ const revenueAgg = await Booking.aggregate([
+ {
+ $match: {
+ paymentStatus: { $in: ["paid"] },
+ },
+ },
+ { $group: { _id: null, total: { $sum: "$totalCost" } } },
+ ]);
+ const totalRevenue = revenueAgg[0]?.total ?? 0;
+
+ // Prosječna ocjena svih hotela
+ const avgRatingAgg = await Hotel.aggregate([
+ { $match: { averageRating: { $gt: 0 } } },
+ { $group: { _id: null, avg: { $avg: "$averageRating" } } },
+ ]);
+ const avgHotelRating = avgRatingAgg[0]?.avg ?? 0;
+
+ const bookingsByMonth = await Booking.aggregate([
+ {
+ $match: {
+ createdAt: {
+ $gte: new Date(new Date().setMonth(new Date().getMonth() - 11)),
+ },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ y: { $year: "$createdAt" },
+ m: { $month: "$createdAt" },
+ },
+ bookings: { $sum: 1 },
+ },
+ },
+ { $sort: { "_id.y": 1, "_id.m": 1 } },
+ ]);
+
+ // Revenue po mjesecima (zadnjih 12)
+ const revenueByMonth = await Booking.aggregate([
+ {
+ $match: {
+ createdAt: {
+ $gte: new Date(new Date().setMonth(new Date().getMonth() - 11)),
+ },
+ paymentStatus: { $in: ["paid"] },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ y: { $year: "$createdAt" },
+ m: { $month: "$createdAt" },
+ },
+ revenue: { $sum: "$totalCost" },
+ },
+ },
+ { $sort: { "_id.y": 1, "_id.m": 1 } },
+ ]);
+
+ // Top 5 hotela po prihodima
+ const topHotelsByRevenue = await Booking.aggregate([
+ {
+ $match: {
+ paymentStatus: { $in: ["paid"] },
+ },
+ },
+ {
+ $group: {
+ _id: "$hotelId",
+ revenue: { $sum: "$totalCost" },
+ bookings: { $sum: 1 },
+ },
+ },
+ { $sort: { revenue: -1 } },
+ { $limit: 5 },
+ {
+ $lookup: {
+ from: "hotels",
+ let: { hid: { $toObjectId: "$_id" } },
+ pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$hid"] } } }],
+ as: "hotel",
+ },
+ },
+ { $unwind: { path: "$hotel", preserveNullAndEmptyArrays: true } },
+ {
+ $project: {
+ hotelId: "$_id",
+ name: "$hotel.name",
+ city: "$hotel.city",
+ revenue: 1,
+ bookings: 1,
+ },
+ },
+ ]);
+
+ // Top 5 hotela po broju booking-a
+ const topHotelsByBookings = await Booking.aggregate([
+ {
+ $group: {
+ _id: "$hotelId",
+ bookings: { $sum: 1 },
+ revenue: { $sum: "$totalCost" },
+ },
+ },
+ { $sort: { bookings: -1 } },
+ { $limit: 5 },
+ {
+ $lookup: {
+ from: "hotels",
+ let: { hid: { $toObjectId: "$_id" } },
+ pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$hid"] } } }],
+ as: "hotel",
+ },
+ },
+ { $unwind: { path: "$hotel", preserveNullAndEmptyArrays: true } },
+ {
+ $project: {
+ hotelId: "$_id",
+ name: "$hotel.name",
+ city: "$hotel.city",
+ bookings: 1,
+ revenue: 1,
+ },
+ },
+ ]);
+
+ const toLabel = (y: number, m: number) =>
+ `${y}-${String(m).padStart(2, "0")}`;
+
+ res.json({
+ kpis: {
+ totalUsers,
+ totalHotels,
+ activeBookings,
+ totalRevenue,
+ avgHotelRating,
+ totalFavorites,
+ },
+ charts: {
+ bookingsByMonth: bookingsByMonth.map((x) => ({
+ month: toLabel(x._id.y, x._id.m),
+ value: x.bookings,
+ })),
+ revenueByMonth: revenueByMonth.map((x) => ({
+ month: toLabel(x._id.y, x._id.m),
+ value: x.revenue,
+ })),
+ topHotelsByRevenue,
+ topHotelsByBookings,
+ },
+ timestamp: new Date().toISOString(),
+ });
+ } catch (e) {
+ console.error(e);
+ res.status(500).json({ message: "Failed to load admin dashboard" });
+ }
+ }
+);
+
+export default router;
diff --git a/hotel-booking-backend/src/routes/auth.ts b/hotel-booking-backend/src/routes/auth.ts
index dfe2fef..0bd1f1b 100644
--- a/hotel-booking-backend/src/routes/auth.ts
+++ b/hotel-booking-backend/src/routes/auth.ts
@@ -4,37 +4,62 @@ import User from "../models/user";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import verifyToken from "../middleware/auth";
+import { authLimiter } from "../middleware/rateLimiter";
const router = express.Router();
+// Helper function for Access and Refresh Tokens
+const createTokens = (userId: string, userRole: string) => {
+ const accessToken = jwt.sign(
+ { userId, userRole },
+ process.env.JWT_SECRET_KEY as string,
+ {
+ expiresIn: "15m", // Access token has short time of expiration
+ }
+ );
+
+ const refreshToken = jwt.sign(
+ { userId },
+ process.env.REFRESH_SECRET_KEY as string,
+ {
+ expiresIn: "7d", // Refresh token has longer time of expiration
+ }
+ );
+
+ return { accessToken, refreshToken };
+};
+
/**
* @swagger
* /api/auth/login:
* post:
* summary: User login
- * description: Authenticate user with email and password
- * tags: [Authentication]
+ * description: Validates email/password, applies rate limiting, and sets auth_token (access) + refresh_token (refresh) as HttpOnly cookies.
+ * tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
- * required:
- * - email
- * - password
+ * required: [email, password]
* properties:
* email:
* type: string
* format: email
- * description: User's email address
+ * example: user@example.com
* password:
* type: string
* minLength: 6
- * description: User's password
+ * example: "secret123"
* responses:
* 200:
- * description: Login successful
+ * description: Login successful (cookies set)
+ * headers:
+ * Set-Cookie:
+ * description: HttpOnly cookies for auth_token and refresh_token
+ * schema:
+ * type: string
* content:
* application/json:
* schema:
@@ -42,14 +67,62 @@ const router = express.Router();
* properties:
* userId:
* type: string
- * description: User ID
+ * example: "64f1c2a9b7d3a2c1f9a1b111"
+ * message:
+ * type: string
+ * example: "Login successful"
+ * user:
+ * type: object
+ * properties:
+ * id:
+ * type: string
+ * example: "64f1c2a9b7d3a2c1f9a1b111"
+ * email:
+ * type: string
+ * format: email
+ * example: user@example.com
+ * firstName:
+ * type: string
+ * example: "John"
+ * lastName:
+ * type: string
+ * example: "Doe"
+ * role:
+ * type: string
+ * example: "user"
* 400:
- * description: Invalid credentials or validation error
+ * description: Validation error or invalid credentials
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * oneOf:
+ * - type: string
+ * - type: array
+ * 429:
+ * description: Too many attempts (rate limit exceeded)
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
* 500:
* description: Server error
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
*/
router.post(
"/login",
+ authLimiter,
[
check("email", "Email is required").isEmail(),
check("password", "Password with 6 or more characters required").isLength({
@@ -75,40 +148,123 @@ router.post(
return res.status(400).json({ message: "Invalid Credentials" });
}
- const token = jwt.sign(
- { userId: user.id },
- process.env.JWT_SECRET_KEY as string,
- {
- expiresIn: "1d",
- }
- );
+ const { accessToken, refreshToken } = createTokens(user.id, user.role);
+
+ res.cookie("auth_token", accessToken, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 15 * 60 * 1000,
+ });
+
+ res.cookie("refresh_token", refreshToken, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 7 * 24 * 60 * 60 * 1000,
+ });
- // Return JWT token in response body for localStorage storage
res.status(200).json({
userId: user._id,
message: "Login successful",
- token: token, // JWT token in response body
user: {
id: user._id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
+ role: user.role,
},
});
} catch (error) {
- console.log(error);
- res.status(500).json({ message: "Something went wrong" });
+ console.error(error);
+ res.status(500).json({ message: "Something went wrong on the server" });
}
}
);
+/**
+ * @swagger
+ * /api/auth/refresh-token:
+ * post:
+ * summary: Refresh access token using refresh token cookie
+ * description: Reads refresh_token from an HttpOnly cookie, validates it, issues a new auth_token (access token), and sets it as an HttpOnly cookie.
+ * tags: [Auth]
+ * responses:
+ * 200:
+ * description: Token refreshed successfully (auth_token cookie set)
+ * headers:
+ * Set-Cookie:
+ * description: New auth_token HttpOnly cookie
+ * schema:
+ * type: string
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
+ * example: "Token refreshed successfully"
+ * userId:
+ * type: string
+ * example: "64f1c2a9b7d3a2c1f9a1b111"
+ * 401:
+ * description: Missing refresh token, invalid/expired token, or user not found (cookies cleared)
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
+ * example: "Invalid or expired refresh token. Please login again."
+ */
+router.post("/refresh-token", async (req: Request, res: Response) => {
+ const refreshToken = req.cookies["refresh_token"];
+
+ if (!refreshToken) {
+ return res.status(401).json({ message: "Access Denied. No refresh token provided." });
+ }
+
+ try {
+ const decoded = jwt.verify(
+ refreshToken,
+ process.env.REFRESH_SECRET_KEY as string
+ ) as { userId: string };
+
+ const user = await User.findById(decoded.userId).select("role");
+
+ if (!user) {
+ return res.status(401).json({ message: "Invalid refresh token or user not found." });
+ }
+
+ const { accessToken } = createTokens(user.id, user.role);
+
+ res.cookie("auth_token", accessToken, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 15 * 60 * 1000,
+ });
+
+ res.status(200).json({ message: "Token refreshed successfully", userId: user.id });
+
+ } catch (err) {
+ // hard logout
+ res.clearCookie("auth_token", { path: "/" });
+ res.clearCookie("refresh_token", { path: "/" });
+ return res.status(401).json({ message: "Invalid or expired refresh token. Please login again." });
+ }
+});
+
+
/**
* @swagger
* /api/auth/validate-token:
* get:
- * summary: Validate authentication token
- * description: Validate the current user's authentication token
- * tags: [Authentication]
+ * summary: Validate auth token
+ * description: Protected route. Uses verifyToken middleware that validates the auth_token cookie.
+ * tags: [Auth]
* security:
* - cookieAuth: []
* responses:
@@ -121,9 +277,16 @@ router.post(
* properties:
* userId:
* type: string
- * description: User ID
+ * example: "64f1c2a9b7d3a2c1f9a1b111"
* 401:
- * description: Token is invalid or expired
+ * description: Token missing or invalid
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
*/
router.get("/validate-token", verifyToken, (req: Request, res: Response) => {
res.status(200).send({ userId: req.userId });
@@ -133,23 +296,42 @@ router.get("/validate-token", verifyToken, (req: Request, res: Response) => {
* @swagger
* /api/auth/logout:
* post:
- * summary: User logout
- * description: Logout user by clearing authentication cookie
- * tags: [Authentication]
+ * summary: Logout user
+ * description: Clears both auth_token and refresh_token HttpOnly cookies.
+ * tags: [Auth]
* responses:
* 200:
- * description: Logout successful
+ * description: Logout successful (cookies cleared)
+ * headers:
+ * Set-Cookie:
+ * description: Cookies are cleared/expired
+ * schema:
+ * type: string
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
+ * example: "Logout successful"
*/
router.post("/logout", (req: Request, res: Response) => {
- res.cookie("session_id", "", {
+ res.clearCookie("auth_token", {
+ expires: new Date(0),
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ path: "/",
+ });
+ res.clearCookie("refresh_token", {
expires: new Date(0),
- maxAge: 0,
- httpOnly: false,
- secure: true,
- sameSite: "none",
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
path: "/",
});
- res.send();
+ res.status(200).send({ message: "Logout successful" });
});
-export default router;
+export default router;
\ No newline at end of file
diff --git a/hotel-booking-backend/src/routes/bookings.ts b/hotel-booking-backend/src/routes/bookings.ts
index 0560f06..3920602 100644
--- a/hotel-booking-backend/src/routes/bookings.ts
+++ b/hotel-booking-backend/src/routes/bookings.ts
@@ -3,28 +3,97 @@ import Booking from "../models/booking";
import Hotel from "../models/hotel";
import User from "../models/user";
import verifyToken from "../middleware/auth";
+import { requireRole } from "../middleware/requireRole";
import { body, param, validationResult } from "express-validator";
const router = express.Router();
+/**
+ * @swagger
+ * tags:
+ * name: Bookings
+ * description: Booking management and administration
+ */
+
+/**
+ * @swagger
+ * /api/bookings:
+ * get:
+ * summary: Get all bookings (admin only)
+ * description: Returns all bookings in the system. Admin role is required.
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: List of bookings returned successfully.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user is not an admin.
+ * 500:
+ * description: Unable to fetch bookings.
+ */
// Get all bookings (admin only)
-router.get("/", verifyToken, async (req: Request, res: Response) => {
- try {
- const bookings = await Booking.find()
- .sort({ createdAt: -1 })
- .populate("hotelId", "name city country");
-
- res.status(200).json(bookings);
- } catch (error) {
- console.log(error);
- res.status(500).json({ message: "Unable to fetch bookings" });
+router.get(
+ "/",
+ verifyToken,
+ requireRole("admin"),
+ async (req: any, res: Response) => {
+ try {
+ console.log("=== DEBUG: Incoming Request ===");
+ console.log("Headers:", req.headers);
+ console.log("Authorization header:", req.headers.authorization);
+ console.log("User ID from verifyToken:", req.userId);
+ console.log("User role from verifyToken:", req.userRole);
+ console.log("RequireRole expecting: admin");
+
+ const bookings = await Booking.find()
+ .sort({ createdAt: -1 })
+ .populate("hotelId", "name city country");
+
+ res.status(200).json(bookings);
+ } catch (error) {
+ console.log("DEBUG ERROR:", error);
+ res.status(500).json({ message: "Unable to fetch bookings" });
+ }
}
-});
+);
+
+/**
+ * @swagger
+ * /api/bookings/hotel/{hotelId}:
+ * get:
+ * summary: Get bookings for a specific hotel (hotel owner only)
+ * description: Returns all bookings for a given hotel. Only the owner of the hotel (or admin, ako dodaš) može pristupiti.
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * description: ID of the hotel.
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: List of bookings for the hotel.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user is not the owner of this hotel.
+ * 404:
+ * description: Hotel not found.
+ * 500:
+ * description: Unable to fetch hotel bookings.
+ */
// Get bookings by hotel ID (for hotel owners)
router.get(
"/hotel/:hotelId",
verifyToken,
+ requireRole("hotel_owner"),
async (req: Request, res: Response) => {
try {
const { hotelId } = req.params;
@@ -35,6 +104,7 @@ router.get(
return res.status(404).json({ message: "Hotel not found" });
}
+ // preporuka: hotel.userId.toString() === req.userId
if (hotel.userId !== req.userId) {
return res.status(403).json({ message: "Access denied" });
}
@@ -51,25 +121,101 @@ router.get(
}
);
+/**
+ * @swagger
+ * /api/bookings/{id}:
+ * get:
+ * summary: Get booking by ID
+ * description: Returns booking details by its ID. RBAC logika treba da odluči ko smije vidjeti (user, hotel_owner, admin).
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * description: Booking ID.
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Booking details.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user has no access to this booking.
+ * 404:
+ * description: Booking not found.
+ * 500:
+ * description: Unable to fetch booking.
+ */
// Get booking by ID
-router.get("/:id", verifyToken, async (req: Request, res: Response) => {
- try {
- const booking = await Booking.findById(req.params.id).populate(
- "hotelId",
- "name city country imageUrls"
- );
-
- if (!booking) {
- return res.status(404).json({ message: "Booking not found" });
- }
+router.get(
+ "/:id",
+ verifyToken,
+ async (req: Request, res: Response) => {
+ try {
+ const booking = await Booking.findById(req.params.id).populate(
+ "hotelId",
+ "name city country imageUrls"
+ );
+
+ if (!booking) {
+ return res.status(404).json({ message: "Booking not found" });
+ }
- res.status(200).json(booking);
- } catch (error) {
- console.log(error);
- res.status(500).json({ message: "Unable to fetch booking" });
+ res.status(200).json(booking);
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: "Unable to fetch booking" });
+ }
}
-});
+);
+/**
+ * @swagger
+ * /api/bookings/{id}/status:
+ * patch:
+ * summary: Update booking status
+ * description: Updates the status of a booking (pending, confirmed, cancelled, completed, refunded). Dozvole zavise od uloge (user/owner/admin).
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * description: Booking ID.
+ * schema:
+ * type: string
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * status:
+ * type: string
+ * enum: [pending, confirmed, cancelled, completed, refunded]
+ * cancellationReason:
+ * type: string
+ * refundAmount:
+ * type: number
+ * responses:
+ * 200:
+ * description: Booking status updated.
+ * 400:
+ * description: Validation error.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user has no access to update this booking.
+ * 404:
+ * description: Booking not found.
+ * 500:
+ * description: Unable to update booking status.
+ */
// Update booking status
router.patch(
"/:id/status",
@@ -114,6 +260,48 @@ router.patch(
}
);
+/**
+ * @swagger
+ * /api/bookings/{id}/payment:
+ * patch:
+ * summary: Update booking payment status
+ * description: Updates the payment status for a booking (pending, paid, failed, refunded).
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * description: Booking ID.
+ * schema:
+ * type: string
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * paymentStatus:
+ * type: string
+ * enum: [pending, paid, failed, refunded]
+ * paymentMethod:
+ * type: string
+ * responses:
+ * 200:
+ * description: Payment status updated.
+ * 400:
+ * description: Validation error.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user has no access to update this payment.
+ * 404:
+ * description: Booking not found.
+ * 500:
+ * description: Unable to update payment status.
+ */
// Update payment status
router.patch(
"/:id/payment",
@@ -155,36 +343,69 @@ router.patch(
}
);
+/**
+ * @swagger
+ * /api/bookings/{id}:
+ * delete:
+ * summary: Delete booking (admin only)
+ * description: Deletes a booking and updates related analytics. Only admin can delete bookings.
+ * tags: [Bookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * description: Booking ID.
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Booking deleted successfully.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 403:
+ * description: Forbidden – user is not an admin.
+ * 404:
+ * description: Booking not found.
+ * 500:
+ * description: Unable to delete booking.
+ */
// Delete booking (admin only)
-router.delete("/:id", verifyToken, async (req: Request, res: Response) => {
- try {
- const booking = await Booking.findByIdAndDelete(req.params.id);
+router.delete(
+ "/:id",
+ verifyToken,
+ requireRole("admin"),
+ async (req: Request, res: Response) => {
+ try {
+ const booking = await Booking.findByIdAndDelete(req.params.id);
- if (!booking) {
- return res.status(404).json({ message: "Booking not found" });
- }
+ if (!booking) {
+ return res.status(404).json({ message: "Booking not found" });
+ }
+
+ // Update hotel analytics
+ await Hotel.findByIdAndUpdate(booking.hotelId, {
+ $inc: {
+ totalBookings: -1,
+ totalRevenue: -(booking.totalCost || 0),
+ },
+ });
- // Update hotel analytics
- await Hotel.findByIdAndUpdate(booking.hotelId, {
- $inc: {
- totalBookings: -1,
- totalRevenue: -(booking.totalCost || 0),
- },
- });
-
- // Update user analytics
- await User.findByIdAndUpdate(booking.userId, {
- $inc: {
- totalBookings: -1,
- totalSpent: -(booking.totalCost || 0),
- },
- });
-
- res.status(200).json({ message: "Booking deleted successfully" });
- } catch (error) {
- console.log(error);
- res.status(500).json({ message: "Unable to delete booking" });
+ // Update user analytics
+ await User.findByIdAndUpdate(booking.userId, {
+ $inc: {
+ totalBookings: -1,
+ totalSpent: -(booking.totalCost || 0),
+ },
+ });
+
+ res.status(200).json({ message: "Booking deleted successfully" });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: "Unable to delete booking" });
+ }
}
-});
+);
export default router;
diff --git a/hotel-booking-backend/src/routes/favorites.ts b/hotel-booking-backend/src/routes/favorites.ts
new file mode 100644
index 0000000..2834628
--- /dev/null
+++ b/hotel-booking-backend/src/routes/favorites.ts
@@ -0,0 +1,107 @@
+import express from "express";
+import verifyToken from "../middleware/auth";
+import Favorite from "../models/favorite";
+
+const router = express.Router();
+
+/**
+ * @swagger
+ * /api/favorites:
+ * get:
+ * summary: Get current user's favorites
+ * tags: [Favorites]
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: List of favorites
+ * 401:
+ * description: Unauthorized
+ */
+router.get("/", verifyToken, async (req, res) => {
+ const favorites = await Favorite.find({ userId: req.userId })
+ .sort({ createdAt: -1 })
+ .lean();
+
+ res.status(200).json(favorites);
+});
+
+/**
+ * @swagger
+ * /api/favorites/{hotelId}:
+ * post:
+ * summary: Add hotel to favorites
+ * tags: [Favorites]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 201:
+ * description: Added to favorites
+ * 409:
+ * description: Already in favorites
+ * 401:
+ * description: Unauthorized
+ */
+router.post("/:hotelId", verifyToken, async (req, res) => {
+ const { hotelId } = req.params;
+
+ try {
+ const favorite = await Favorite.create({
+ userId: req.userId,
+ hotelId,
+ });
+
+ return res.status(201).json(favorite);
+ } catch (err: any) {
+ // unique index error (duplicate favorite)
+ if (err?.code === 11000) {
+ return res.status(409).json({ message: "Already in favorites" });
+ }
+ return res.status(500).json({ message: "Failed to add favorite" });
+ }
+});
+
+/**
+ * @swagger
+ * /api/favorites/{hotelId}:
+ * delete:
+ * summary: Remove hotel from favorites
+ * tags: [Favorites]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Removed from favorites
+ * 404:
+ * description: Favorite not found
+ * 401:
+ * description: Unauthorized
+ */
+router.delete("/:hotelId", verifyToken, async (req, res) => {
+ const { hotelId } = req.params;
+
+ const result = await Favorite.findOneAndDelete({
+ userId: req.userId,
+ hotelId,
+ });
+
+ if (!result) {
+ return res.status(404).json({ message: "Favorite not found" });
+ }
+
+ res.status(200).json({ message: "Removed from favorites" });
+});
+
+export default router;
diff --git a/hotel-booking-backend/src/routes/hotels.ts b/hotel-booking-backend/src/routes/hotels.ts
index 7201bc5..d295f38 100644
--- a/hotel-booking-backend/src/routes/hotels.ts
+++ b/hotel-booking-backend/src/routes/hotels.ts
@@ -3,14 +3,81 @@ import Hotel from "../models/hotel";
import Booking from "../models/booking";
import User from "../models/user";
import { BookingType, HotelSearchResponse } from "../../../shared/types";
-import { param, validationResult } from "express-validator";
+import { body, param, validationResult } from "express-validator";
import Stripe from "stripe";
import verifyToken from "../middleware/auth";
-const stripe = new Stripe(process.env.STRIPE_API_KEY as string);
-
+const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string);
const router = express.Router();
+const MS_PER_DAY = 1000 * 60 * 60 * 24;
+
+const toMidnightUTC = (d: Date) =>
+ new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
+
+const diffNights = (start: Date, end: Date) => {
+ const s = toMidnightUTC(start);
+ const e = toMidnightUTC(end);
+ return (e.getTime() - s.getTime()) / MS_PER_DAY;
+};
+
+/**
+ * @swagger
+ * tags:
+ * - name: Hotels
+ * description: Public hotel search & details
+ * - name: HotelBookings
+ * description: Payment and booking operations for a selected hotel
+ */
+
+/**
+ * @swagger
+ * /api/hotels/search:
+ * get:
+ * summary: Search hotels using various filters
+ * tags: [Hotels]
+ * parameters:
+ * - name: destination
+ * in: query
+ * schema:
+ * type: string
+ * description: City or country
+ * - name: facilities
+ * in: query
+ * schema:
+ * type: array
+ * items: { type: string }
+ * description: Facility filter (array or single)
+ * - name: types
+ * in: query
+ * schema:
+ * type: array
+ * items: { type: string }
+ * description: Hotel types
+ * - name: stars
+ * in: query
+ * schema:
+ * type: array
+ * items: { type: number }
+ * - name: maxPrice
+ * in: query
+ * schema:
+ * type: number
+ * - name: sortOption
+ * in: query
+ * schema:
+ * type: string
+ * enum: [starRating, pricePerNightAsc, pricePerNightDesc]
+ * - name: page
+ * in: query
+ * schema:
+ * type: number
+ * responses:
+ * 200:
+ * description: List of filtered hotels with pagination metadata
+ * 500:
+ * description: Something went wrong
+ */
router.get("/search", async (req: Request, res: Response) => {
try {
const query = constructSearchQuery(req.query);
@@ -29,9 +96,7 @@ router.get("/search", async (req: Request, res: Response) => {
}
const pageSize = 5;
- const pageNumber = parseInt(
- req.query.page ? req.query.page.toString() : "1"
- );
+ const pageNumber = parseInt(req.query.page ? req.query.page.toString() : "1");
const skip = (pageNumber - 1) * pageSize;
const hotels = await Hotel.find(query)
@@ -57,6 +122,18 @@ router.get("/search", async (req: Request, res: Response) => {
}
});
+/**
+ * @swagger
+ * /api/hotels:
+ * get:
+ * summary: Get all hotels (public)
+ * tags: [Hotels]
+ * responses:
+ * 200:
+ * description: Successfully retrieved hotel list
+ * 500:
+ * description: Error fetching hotels
+ */
router.get("/", async (req: Request, res: Response) => {
try {
const hotels = await Hotel.find().sort("-lastUpdated");
@@ -67,6 +144,27 @@ router.get("/", async (req: Request, res: Response) => {
}
});
+/**
+ * @swagger
+ * /api/hotels/{id}:
+ * get:
+ * summary: Get hotel details by ID
+ * tags: [Hotels]
+ * parameters:
+ * - name: id
+ * in: path
+ * required: true
+ * description: Hotel ID
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Hotel details returned
+ * 400:
+ * description: Validation error
+ * 500:
+ * description: Something went wrong
+ */
router.get(
"/:id",
[param("id").notEmpty().withMessage("Hotel ID is required")],
@@ -88,47 +186,180 @@ router.get(
}
);
+/**
+ * @swagger
+ * /api/hotels/{hotelId}/bookings/payment-intent:
+ * post:
+ * summary: Create a Stripe payment intent for a hotel booking
+ * tags: [HotelBookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - name: hotelId
+ * in: path
+ * required: true
+ * schema: { type: string }
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * numberOfNights:
+ * type: number
+ * example: 3
+ * responses:
+ * 200:
+ * description: Payment intent created
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * paymentIntentId: { type: string }
+ * clientSecret: { type: string }
+ * totalCost: { type: number }
+ * 400:
+ * description: Hotel not found
+ * 500:
+ * description: Stripe error or server failure
+ */
router.post(
"/:hotelId/bookings/payment-intent",
verifyToken,
+ [
+ body("numberOfNights")
+ .isInt({ min: 1 })
+ .withMessage("numberOfNights must be an integer >= 1"),
+ ],
async (req: Request, res: Response) => {
- const { numberOfNights } = req.body;
- const hotelId = req.params.hotelId;
-
- const hotel = await Hotel.findById(hotelId);
- if (!hotel) {
- return res.status(400).json({ message: "Hotel not found" });
+ const errors = validationResult(req);
+ if (!errors.isEmpty()) {
+ return res.status(400).json({ errors: errors.array() });
}
- const totalCost = hotel.pricePerNight * numberOfNights;
+ try {
+ const { numberOfNights } = req.body;
+ const hotelId = req.params.hotelId;
- const paymentIntent = await stripe.paymentIntents.create({
- amount: totalCost * 100,
- currency: "gbp",
- metadata: {
- hotelId,
- userId: req.userId,
- },
- });
+ const hotel = await Hotel.findById(hotelId);
+ if (!hotel) {
+ return res.status(400).json({ message: "Hotel not found" });
+ }
- if (!paymentIntent.client_secret) {
- return res.status(500).json({ message: "Error creating payment intent" });
- }
+ const totalCost = hotel.pricePerNight * Number(numberOfNights);
- const response = {
- paymentIntentId: paymentIntent.id,
- clientSecret: paymentIntent.client_secret.toString(),
- totalCost,
- };
+ const paymentIntent = await stripe.paymentIntents.create({
+ amount: Math.round(totalCost * 100),
+ currency: "gbp",
+ metadata: {
+ hotelId,
+ userId: req.userId,
+ },
+ });
+
+ if (!paymentIntent.client_secret) {
+ return res.status(500).json({ message: "Error creating payment intent" });
+ }
- res.send(response);
+ res.send({
+ paymentIntentId: paymentIntent.id,
+ clientSecret: paymentIntent.client_secret.toString(),
+ totalCost,
+ });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: "Something went wrong" });
+ }
}
);
+/**
+ * @swagger
+ * /api/hotels/{hotelId}/bookings:
+ * post:
+ * summary: Confirm a booking after successful Stripe payment
+ * tags: [HotelBookings]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - name: hotelId
+ * in: path
+ * required: true
+ * schema: { type: string }
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * paymentIntentId:
+ * type: string
+ * totalCost:
+ * type: number
+ * startDate:
+ * type: string
+ * endDate:
+ * type: string
+ * responses:
+ * 200:
+ * description: Booking confirmed
+ * 400:
+ * description: Payment intent mismatch or unpaid
+ * 500:
+ * description: Server error while saving booking
+ */
router.post(
"/:hotelId/bookings",
verifyToken,
+ [
+ body("paymentIntentId")
+ .notEmpty()
+ .withMessage("paymentIntentId is required"),
+
+ body("totalCost")
+ .isNumeric()
+ .withMessage("totalCost must be a number"),
+
+ body("startDate")
+ .notEmpty()
+ .withMessage("startDate is required")
+ .isISO8601()
+ .withMessage("startDate must be a valid ISO date"),
+
+ body("endDate")
+ .notEmpty()
+ .withMessage("endDate is required")
+ .isISO8601()
+ .withMessage("endDate must be a valid ISO date"),
+
+ // bar 1 noćenje (endDate mora biti nakon startDate)
+ body().custom((_, { req }) => {
+ const start = new Date(req.body.startDate);
+ const end = new Date(req.body.endDate);
+
+ const nights = diffNights(start, end);
+ if (nights < 1) {
+ throw new Error(
+ "Booking must be at least 1 night (endDate must be after startDate)"
+ );
+ }
+
+ if (req.body.numberOfNights && Number(req.body.numberOfNights) !== nights) {
+ throw new Error("numberOfNights mismatch");
+ }
+
+ return true;
+ }),
+ ],
async (req: Request, res: Response) => {
+ const errors = validationResult(req);
+ if (!errors.isEmpty()) {
+ return res.status(400).json({ errors: errors.array() });
+ }
+
try {
const paymentIntentId = req.body.paymentIntentId;
@@ -157,16 +388,14 @@ router.post(
...req.body,
userId: req.userId,
hotelId: req.params.hotelId,
- createdAt: new Date(), // Add booking creation timestamp
- status: "confirmed", // Set initial status
- paymentStatus: "paid", // Set payment status since payment succeeded
+ createdAt: new Date(),
+ status: "confirmed",
+ paymentStatus: "paid",
};
- // Create booking in separate collection
const booking = new Booking(newBooking);
await booking.save();
- // Update hotel analytics
await Hotel.findByIdAndUpdate(req.params.hotelId, {
$inc: {
totalBookings: 1,
@@ -174,7 +403,6 @@ router.post(
},
});
- // Update user analytics
await User.findByIdAndUpdate(req.userId, {
$inc: {
totalBookings: 1,
@@ -193,7 +421,7 @@ router.post(
const constructSearchQuery = (queryParams: any) => {
let constructedQuery: any = {};
- if (queryParams.destination && queryParams.destination.trim() !== "") {
+ if (queryParams.destination?.trim()) {
const destination = queryParams.destination.trim();
constructedQuery.$or = [
@@ -202,18 +430,6 @@ const constructSearchQuery = (queryParams: any) => {
];
}
- if (queryParams.adultCount) {
- constructedQuery.adultCount = {
- $gte: parseInt(queryParams.adultCount),
- };
- }
-
- if (queryParams.childCount) {
- constructedQuery.childCount = {
- $gte: parseInt(queryParams.childCount),
- };
- }
-
if (queryParams.facilities) {
constructedQuery.facilities = {
$all: Array.isArray(queryParams.facilities)
@@ -233,17 +449,31 @@ const constructSearchQuery = (queryParams: any) => {
if (queryParams.stars) {
const starRatings = Array.isArray(queryParams.stars)
? queryParams.stars.map((star: string) => parseInt(star))
- : parseInt(queryParams.stars);
-
+ : [parseInt(queryParams.stars)];
constructedQuery.starRating = { $in: starRatings };
}
if (queryParams.maxPrice) {
constructedQuery.pricePerNight = {
- $lte: parseInt(queryParams.maxPrice).toString(),
+ $lte: parseInt(queryParams.maxPrice),
};
}
+ // Guests capacity filtering
+ if (queryParams.adultCount) {
+ const adults = parseInt(queryParams.adultCount);
+ if (!isNaN(adults)) {
+ constructedQuery.adultCount = { $gte: adults };
+ }
+ }
+
+ if (queryParams.childCount) {
+ const children = parseInt(queryParams.childCount);
+ if (!isNaN(children)) {
+ constructedQuery.childCount = { $gte: children };
+ }
+ }
+
return constructedQuery;
};
diff --git a/hotel-booking-backend/src/routes/my-bookings.ts b/hotel-booking-backend/src/routes/my-bookings.ts
index c12289d..c8aad32 100644
--- a/hotel-booking-backend/src/routes/my-bookings.ts
+++ b/hotel-booking-backend/src/routes/my-bookings.ts
@@ -5,33 +5,99 @@ import Booking from "../models/booking";
const router = express.Router();
-// /api/my-bookings
+/**
+ * @swagger
+ * tags:
+ * name: MyBookings
+ * description: View bookings made by the authenticated user
+ */
+
+/**
+ * @swagger
+ * /api/my-bookings:
+ * get:
+ * summary: Get all bookings for the authenticated user
+ * description: >
+ * Returns a list of hotels, each containing the booking(s) the user has made for that hotel.
+ * This endpoint aggregates booking data with hotel details.
+ *
+ * tags: [MyBookings]
+ *
+ * security:
+ * - bearerAuth: []
+ *
+ * responses:
+ * 200:
+ * description: Successfully returned user's bookings grouped by hotel.
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * type: object
+ * properties:
+ * _id:
+ * type: string
+ * description: Hotel ID
+ * name:
+ * type: string
+ * city:
+ * type: string
+ * country:
+ * type: string
+ * description:
+ * type: string
+ * imageUrls:
+ * type: array
+ * items:
+ * type: string
+ * bookings:
+ * type: array
+ * description: User's bookings for this hotel
+ * items:
+ * type: object
+ * properties:
+ * _id:
+ * type: string
+ * userId:
+ * type: string
+ * hotelId:
+ * type: string
+ * startDate:
+ * type: string
+ * format: date
+ * endDate:
+ * type: string
+ * format: date
+ * totalCost:
+ * type: number
+ * status:
+ * type: string
+ *
+ * 401:
+ * description: Unauthorized – token missing or invalid.
+ *
+ * 500:
+ * description: Internal server error – unable to fetch bookings.
+ */
router.get("/", verifyToken, async (req: Request, res: Response) => {
try {
- // Get user's bookings from separate collection
const userBookings = await Booking.find({ userId: req.userId }).sort({
createdAt: -1,
});
- // Get hotel details for each booking
const results = await Promise.all(
userBookings.map(async (booking) => {
const hotel = await Hotel.findById(booking.hotelId);
- if (!hotel) {
- return null;
- }
+ if (!hotel) return null;
- // Create response object with hotel and booking data
- const hotelWithUserBookings = {
+ return {
...hotel.toObject(),
bookings: [booking.toObject()],
};
-
- return hotelWithUserBookings;
})
);
- // Filter out null results and send
const validResults = results.filter((result) => result !== null);
res.status(200).send(validResults);
} catch (error) {
diff --git a/hotel-booking-backend/src/routes/my-hotels.ts b/hotel-booking-backend/src/routes/my-hotels.ts
index f86f1dc..adb9d21 100644
--- a/hotel-booking-backend/src/routes/my-hotels.ts
+++ b/hotel-booking-backend/src/routes/my-hotels.ts
@@ -1,10 +1,12 @@
import express, { Request, Response } from "express";
import multer from "multer";
-import cloudinary from "cloudinary";
import Hotel from "../models/hotel";
import verifyToken from "../middleware/auth";
import { body } from "express-validator";
import { HotelType } from "../../../shared/types";
+import { bucket } from "../firebase";
+import { v4 as uuidv4 } from "uuid";
+import { requireRole } from "../middleware/requireRole";
const router = express.Router();
@@ -16,9 +18,144 @@ const upload = multer({
},
});
+/**
+ * @swagger
+ * tags:
+ * name: MyHotels
+ * description: CRUD operations for hotels owned by the authenticated user
+ */
+
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * Hotel:
+ * type: object
+ * properties:
+ * _id:
+ * type: string
+ * name:
+ * type: string
+ * city:
+ * type: string
+ * country:
+ * type: string
+ * description:
+ * type: string
+ * type:
+ * type: array
+ * items:
+ * type: string
+ * pricePerNight:
+ * type: number
+ * facilities:
+ * type: array
+ * items:
+ * type: string
+ * imageUrls:
+ * type: array
+ * items:
+ * type: string
+ * contact:
+ * type: object
+ * properties:
+ * phone:
+ * type: string
+ * email:
+ * type: string
+ * website:
+ * type: string
+ * policies:
+ * type: object
+ * properties:
+ * checkInTime:
+ * type: string
+ * checkOutTime:
+ * type: string
+ * cancellationPolicy:
+ * type: string
+ * petPolicy:
+ * type: string
+ * smokingPolicy:
+ * type: string
+ */
+
+// CREATE HOTEL
+/**
+ * @swagger
+ * /api/my-hotels:
+ * post:
+ * summary: Create a new hotel (owned by the authenticated user)
+ * description: Creates a new hotel and uploads up to 6 images to Firebase Storage. Requires authentication (hotel owner / admin).
+ * tags: [MyHotels]
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * multipart/form-data:
+ * schema:
+ * type: object
+ * properties:
+ * name:
+ * type: string
+ * city:
+ * type: string
+ * country:
+ * type: string
+ * description:
+ * type: string
+ * type:
+ * type: array
+ * items:
+ * type: string
+ * description: One or more hotel types.
+ * pricePerNight:
+ * type: number
+ * facilities:
+ * type: array
+ * items:
+ * type: string
+ * "contact.phone":
+ * type: string
+ * "contact.email":
+ * type: string
+ * "contact.website":
+ * type: string
+ * "policies.checkInTime":
+ * type: string
+ * "policies.checkOutTime":
+ * type: string
+ * "policies.cancellationPolicy":
+ * type: string
+ * "policies.petPolicy":
+ * type: string
+ * "policies.smokingPolicy":
+ * type: string
+ * imageFiles:
+ * type: array
+ * items:
+ * type: string
+ * format: binary
+ * description: Up to 6 image files.
+ * responses:
+ * 201:
+ * description: Hotel created successfully.
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/Hotel'
+ * 400:
+ * description: Validation error.
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 500:
+ * description: Server error.
+ */
router.post(
"/",
verifyToken,
+ requireRole("hotel_owner"),
[
body("name").notEmpty().withMessage("Name is required"),
body("city").notEmpty().withMessage("City is required"),
@@ -40,15 +177,13 @@ router.post(
upload.array("imageFiles", 6),
async (req: Request, res: Response) => {
try {
- const imageFiles = (req as any).files as any[];
- const newHotel: HotelType = req.body;
+ const imageFiles = (req as any).files as Express.Multer.File[];
+ const newHotel: HotelType = req.body as any;
- // Ensure type is always an array
if (typeof newHotel.type === "string") {
newHotel.type = [newHotel.type];
}
- // Handle nested objects from FormData
newHotel.contact = {
phone: req.body["contact.phone"] || "",
email: req.body["contact.email"] || "",
@@ -67,7 +202,7 @@ router.post(
newHotel.imageUrls = imageUrls;
newHotel.lastUpdated = new Date();
- newHotel.userId = req.userId;
+ newHotel.userId = (req as any).userId;
const hotel = new Hotel(newHotel);
await hotel.save();
@@ -80,49 +215,203 @@ router.post(
}
);
-router.get("/", verifyToken, async (req: Request, res: Response) => {
+// GET ALL HOTELS (owned by current user)
+/**
+ * @swagger
+ * /api/my-hotels:
+ * get:
+ * summary: Get all hotels for the authenticated user
+ * description: Returns all hotels where userId matches the authenticated user.
+ * tags: [MyHotels]
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: List of hotels.
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/Hotel'
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 500:
+ * description: Error fetching hotels.
+ */
+router.get("/", verifyToken, requireRole("hotel_owner"), async (req: Request, res: Response) => {
try {
- const hotels = await Hotel.find({ userId: req.userId });
+ const hotels = await Hotel.find({ userId: (req as any).userId });
res.json(hotels);
} catch (error) {
res.status(500).json({ message: "Error fetching hotels" });
}
});
-router.get("/:id", verifyToken, async (req: Request, res: Response) => {
+// GET ONE HOTEL (owned by current user)
+/**
+ * @swagger
+ * /api/my-hotels/{id}:
+ * get:
+ * summary: Get a single hotel by ID (owned by the authenticated user)
+ * description: Returns a single hotel document if it belongs to the authenticated user.
+ * tags: [MyHotels]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: id
+ * required: true
+ * description: Hotel ID.
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Hotel data.
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/Hotel'
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 404:
+ * description: Hotel not found or does not belong to user.
+ * 500:
+ * description: Error fetching hotel.
+ */
+router.get("/:id", verifyToken, requireRole("hotel_owner"), async (req: Request, res: Response) => {
const id = req.params.id.toString();
+
try {
const hotel = await Hotel.findOne({
_id: id,
- userId: req.userId,
+ userId: (req as any).userId,
});
+ if (!hotel) {
+ return res.status(404).json({ message: "Hotel not found" });
+ }
res.json(hotel);
} catch (error) {
res.status(500).json({ message: "Error fetching hotels" });
}
});
+// UPDATE HOTEL (owned by current user)
+/**
+ * @swagger
+ * /api/my-hotels/{hotelId}:
+ * put:
+ * summary: Update an existing hotel (owned by the authenticated user)
+ * description: Updates hotel details and optionally uploads new images. Existing images can be preserved by sending their URLs in imageUrls field.
+ * tags: [MyHotels]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * description: Hotel ID.
+ * schema:
+ * type: string
+ * requestBody:
+ * required: true
+ * content:
+ * multipart/form-data:
+ * schema:
+ * type: object
+ * properties:
+ * name:
+ * type: string
+ * city:
+ * type: string
+ * country:
+ * type: string
+ * description:
+ * type: string
+ * type:
+ * type: array
+ * items:
+ * type: string
+ * pricePerNight:
+ * type: number
+ * starRating:
+ * type: number
+ * adultCount:
+ * type: number
+ * childCount:
+ * type: number
+ * facilities:
+ * type: array
+ * items:
+ * type: string
+ * "contact.phone":
+ * type: string
+ * "contact.email":
+ * type: string
+ * "contact.website":
+ * type: string
+ * "policies.checkInTime":
+ * type: string
+ * "policies.checkOutTime":
+ * type: string
+ * "policies.cancellationPolicy":
+ * type: string
+ * "policies.petPolicy":
+ * type: string
+ * "policies.smokingPolicy":
+ * type: string
+ * imageUrls:
+ * type: array
+ * items:
+ * type: string
+ * description: Existing image URLs to keep.
+ * imageFiles:
+ * type: array
+ * items:
+ * type: string
+ * format: binary
+ * description: New image files to upload.
+ * responses:
+ * 200:
+ * description: Hotel updated successfully.
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/Hotel'
+ * 401:
+ * description: Unauthorized – missing or invalid token.
+ * 404:
+ * description: Hotel not found or not owned by user.
+ * 500:
+ * description: Error updating hotel.
+ */
router.put(
"/:hotelId",
verifyToken,
- upload.array("imageFiles"),
+ requireRole("hotel_owner"),
+ upload.array("imageFiles", 6),
async (req: Request, res: Response) => {
try {
- console.log("Request body:", req.body);
- console.log("Hotel ID:", req.params.hotelId);
- console.log("User ID:", req.userId);
-
- // First, find the existing hotel
- const existingHotel = await Hotel.findOne({
- _id: req.params.hotelId,
- userId: req.userId,
- });
+ const userId = (req as any).userId;
+ const hotelId = req.params.hotelId;
+ const existingHotel = await Hotel.findOne({ _id: hotelId, userId });
if (!existingHotel) {
return res.status(404).json({ message: "Hotel not found" });
}
- // Prepare update data
+ const keptImageUrls = parseImageUrls(req.body.imageUrls);
+
+ const files = (req as any).files as Express.Multer.File[];
+ const newImageUrls =
+ files && files.length > 0 ? await uploadImages(files) : [];
+
+ const finalImageUrls = [...keptImageUrls, ...newImageUrls];
+
+ const removedImageUrls = (existingHotel.imageUrls || []).filter(
+ (url: string) => !keptImageUrls.includes(url)
+ );
+
const updateData: any = {
name: req.body.name,
city: req.body.city,
@@ -137,82 +426,108 @@ router.put(
? req.body.facilities
: [req.body.facilities],
lastUpdated: new Date(),
+ imageUrls: finalImageUrls,
+ contact: {
+ phone: req.body["contact.phone"] || "",
+ email: req.body["contact.email"] || "",
+ website: req.body["contact.website"] || "",
+ },
+ policies: {
+ checkInTime: req.body["policies.checkInTime"] || "",
+ checkOutTime: req.body["policies.checkOutTime"] || "",
+ cancellationPolicy: req.body["policies.cancellationPolicy"] || "",
+ petPolicy: req.body["policies.petPolicy"] || "",
+ smokingPolicy: req.body["policies.smokingPolicy"] || "",
+ },
};
- // Handle contact information
- updateData.contact = {
- phone: req.body["contact.phone"] || "",
- email: req.body["contact.email"] || "",
- website: req.body["contact.website"] || "",
- };
-
- // Handle policies
- updateData.policies = {
- checkInTime: req.body["policies.checkInTime"] || "",
- checkOutTime: req.body["policies.checkOutTime"] || "",
- cancellationPolicy: req.body["policies.cancellationPolicy"] || "",
- petPolicy: req.body["policies.petPolicy"] || "",
- smokingPolicy: req.body["policies.smokingPolicy"] || "",
- };
-
- console.log("Update data:", updateData);
-
- // Update the hotel
- const updatedHotel = await Hotel.findByIdAndUpdate(
- req.params.hotelId,
- updateData,
- { new: true }
- );
+ const updatedHotel = await Hotel.findByIdAndUpdate(hotelId, updateData, {
+ new: true,
+ });
if (!updatedHotel) {
return res.status(404).json({ message: "Hotel not found" });
}
- // Handle image uploads if any
- const files = (req as any).files as any[];
- if (files && files.length > 0) {
- const updatedImageUrls = await uploadImages(files);
- updatedHotel.imageUrls = [
- ...updatedImageUrls,
- ...(req.body.imageUrls
- ? Array.isArray(req.body.imageUrls)
- ? req.body.imageUrls
- : [req.body.imageUrls]
- : []),
- ];
- await updatedHotel.save();
- }
+ await Promise.all(
+ removedImageUrls.map(async (url: string) => {
+ const path = getFirebasePathFromDownloadUrl(url);
+ if (!path) return;
+ try {
+ await bucket.file(path).delete({ ignoreNotFound: true });
+ } catch (e) {
+ console.warn("Failed to delete file from Firebase:", path, e);
+ }
+ })
+ );
- res.status(200).json(updatedHotel);
- } catch (error) {
+ return res.status(200).json(updatedHotel);
+ } catch (error: any) {
console.error("Error updating hotel:", error);
- console.error("Request body:", req.body);
- console.error("Hotel ID:", req.params.hotelId);
- console.error("User ID:", req.userId);
res.status(500).json({
message: "Something went wrong",
- error: error instanceof Error ? error.message : "Unknown error",
+ error: error.message || "Unknown error",
});
}
}
);
-async function uploadImages(imageFiles: any[]) {
- const uploadPromises = imageFiles.map(async (image) => {
- const b64 = Buffer.from(image.buffer as Uint8Array).toString("base64");
- let dataURI = "data:" + image.mimetype + ";base64," + b64;
- const res = await cloudinary.v2.uploader.upload(dataURI, {
- secure: true, // Force HTTPS URLs
- transformation: [
- { width: 800, height: 600, crop: "fill" },
- { quality: "auto" },
- ],
+
+function parseImageUrls(value: any): string[] {
+ if (!value) return [];
+
+ if (Array.isArray(value)) return value.filter(Boolean);
+
+ if (typeof value === "string") {
+ const trimmed = value.trim();
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
+ try {
+ const parsed = JSON.parse(trimmed);
+ return Array.isArray(parsed) ? parsed.filter(Boolean) : [];
+ } catch {
+ return [];
+ }
+ }
+ return [value];
+ }
+
+ return [];
+}
+
+function getFirebasePathFromDownloadUrl(url: string): string | null {
+ const marker = "/o/";
+ const idx = url.indexOf(marker);
+ if (idx === -1) return null;
+
+ const after = url.substring(idx + marker.length);
+ const pathEncoded = after.split("?")[0];
+ if (!pathEncoded) return null;
+
+ return decodeURIComponent(pathEncoded);
+}
+
+
+// FIREBASE UPLOAD FUNCTION
+async function uploadImages(imageFiles: Express.Multer.File[]) {
+ const uploadPromises = imageFiles.map(async (file) => {
+ const filename = `hotels/${uuidv4()}-${file.originalname}`;
+ const fileUpload = bucket.file(filename);
+
+ await fileUpload.save(file.buffer, {
+ metadata: {
+ contentType: file.mimetype,
+ metadata: {
+ firebaseStorageDownloadTokens: uuidv4(),
+ },
+ },
});
- return res.url;
+
+ return `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
+ filename
+ )}?alt=media`;
});
- const imageUrls = await Promise.all(uploadPromises);
- return imageUrls;
+ return Promise.all(uploadPromises);
}
export default router;
diff --git a/hotel-booking-backend/src/routes/reviews.ts b/hotel-booking-backend/src/routes/reviews.ts
new file mode 100644
index 0000000..5f8859b
--- /dev/null
+++ b/hotel-booking-backend/src/routes/reviews.ts
@@ -0,0 +1,161 @@
+import express from "express";
+import Review from "../models/review";
+import Hotel from "../models/hotel";
+import Booking from "../models/booking";
+import verifyToken from "../middleware/auth";
+
+const router = express.Router();
+
+/**
+ * @swagger
+ * /api/reviews/{hotelId}:
+ * post:
+ * summary: Create a review for a hotel
+ * tags: [Reviews]
+ * security:
+ * - bearerAuth: []
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * schema:
+ * type: string
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required: [rating, comment, categories]
+ * properties:
+ * rating:
+ * type: number
+ * example: 5
+ * comment:
+ * type: string
+ * categories:
+ * type: object
+ * properties:
+ * cleanliness: { type: number, example: 5 }
+ * service: { type: number, example: 5 }
+ * location: { type: number, example: 4 }
+ * value: { type: number, example: 5 }
+ * amenities: { type: number, example: 4 }
+ * responses:
+ * 201:
+ * description: Review created
+ * 401:
+ * description: Unauthorized
+ */
+router.post("/:hotelId", verifyToken, async (req, res) => {
+ const { rating, comment, categories } = req.body;
+ const userId = req.userId;
+ const hotelId = req.params.hotelId;
+
+ const booking = await Booking.findOne({
+ userId,
+ hotelId,
+ paymentStatus: "paid",
+ });
+
+ if (!booking) {
+ return res.status(403).json({
+ message: "You can only review hotels you have stayed in",
+ });
+ }
+
+ const existingReview = await Review.findOne({
+ userId,
+ hotelId,
+ });
+
+ if (existingReview) {
+ return res.status(400).json({
+ message: "You already reviewed this hotel",
+ });
+ }
+
+ const review = await Review.create({
+ userId,
+ hotelId,
+ bookingId: booking._id,
+ rating,
+ comment,
+ categories,
+ isVerified: true,
+ });
+
+ const stats = await Review.aggregate([
+ { $match: { hotelId } },
+ {
+ $group: {
+ _id: null,
+ avgRating: { $avg: "$rating" },
+ count: { $sum: 1 },
+ },
+ },
+ ]);
+
+ await Hotel.findByIdAndUpdate(hotelId, {
+ averageRating: stats[0]?.avgRating || 0,
+ reviewCount: stats[0]?.count || 0,
+ });
+
+ res.status(201).json(review);
+});
+
+/**
+ * @swagger
+ * /api/reviews/{hotelId}:
+ * get:
+ * summary: Get all reviews for a hotel
+ * tags: [Reviews]
+ * parameters:
+ * - in: path
+ * name: hotelId
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: List of reviews
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * type: object
+ * properties:
+ * _id:
+ * type: string
+ * userId:
+ * type: string
+ * rating:
+ * type: number
+ * comment:
+ * type: string
+ * categories:
+ * type: object
+ * properties:
+ * cleanliness: { type: number }
+ * service: { type: number }
+ * location: { type: number }
+ * value: { type: number }
+ * amenities: { type: number }
+ * createdAt:
+ * type: string
+ * format: date-time
+ * 404:
+ * description: No reviews found
+ */
+router.get("/:hotelId", async (req, res) => {
+ const { hotelId } = req.params;
+
+ const reviews = await Review.find({ hotelId })
+ .sort({ createdAt: -1 })
+ .lean();
+
+ return res.status(200).json(reviews);
+});
+
+export default router;
diff --git a/hotel-booking-backend/src/routes/users.ts b/hotel-booking-backend/src/routes/users.ts
index 7535639..17628da 100644
--- a/hotel-booking-backend/src/routes/users.ts
+++ b/hotel-booking-backend/src/routes/users.ts
@@ -3,73 +3,118 @@ import User from "../models/user";
import jwt from "jsonwebtoken";
import { check, validationResult } from "express-validator";
import verifyToken from "../middleware/auth";
+import { authLimiter } from "../middleware/rateLimiter"; // Pretpostavlja da je ovo kreirano
const router = express.Router();
-router.get("/me", verifyToken, async (req: Request, res: Response) => {
- const userId = req.userId;
-
- try {
- const user = await User.findById(userId).select("-password");
- if (!user) {
- return res.status(400).json({ message: "User not found" });
+// Pomoćna funkcija za kreiranje Access i Refresh Tokena
+const createTokens = (userId: string, userRole: string) => {
+ // SIGURNOSNA PROVJERA: Da li su ključevi prisutni?
+ if (!process.env.JWT_SECRET_KEY || !process.env.REFRESH_SECRET_KEY) {
+ console.error("CRITICAL ERROR: JWT or REFRESH secret key is missing!");
+ // Bacanje izuzetka koji će uhvatiti catch blok u ruti
+ throw new Error("Server configuration error: Token keys missing.");
}
- res.json(user);
- } catch (error) {
- console.log(error);
- res.status(500).json({ message: "something went wrong" });
- }
-});
+
+ // Kreiranje Access Tokena (kratkotrajni: 15m)
+ const accessToken = jwt.sign(
+ { userId, userRole },
+ process.env.JWT_SECRET_KEY as string,
+ {
+ expiresIn: "15m",
+ }
+ );
+
+ // Kreiranje Refresh Tokena (dugotrajni: 7d)
+ const refreshToken = jwt.sign(
+ { userId },
+ process.env.REFRESH_SECRET_KEY as string,
+ {
+ expiresIn: "7d",
+ }
+ );
+
+ return { accessToken, refreshToken };
+};
+
router.post(
- "/register",
- [
- check("firstName", "First Name is required").isString(),
- check("lastName", "Last Name is required").isString(),
- check("email", "Email is required").isEmail(),
- check("password", "Password with 6 or more characters required").isLength({
- min: 6,
- }),
- ],
- async (req: Request, res: Response) => {
- const errors = validationResult(req);
- if (!errors.isEmpty()) {
- return res.status(400).json({ message: errors.array() });
- }
+ "/register",
+ authLimiter, // Primjena Rate Limitinga
+ [
+ check("firstName", "First Name is required").isString(),
+ check("lastName", "Last Name is required").isString(),
+ check("email", "Email is required").isEmail(),
+ check("password", "Password with 6 or more characters required").isLength({
+ min: 6,
+ }),
+ ],
+ async (req: Request, res: Response) => {
+ const errors = validationResult(req);
+ if (!errors.isEmpty()) {
+ return res.status(400).json({ message: errors.array() });
+ }
- try {
- let user = await User.findOne({
- email: req.body.email,
- });
+ try {
+ let user = await User.findOne({
+ email: req.body.email,
+ });
- if (user) {
- return res.status(400).json({ message: "User already exists" });
- }
+ if (user) {
+ // Koristimo 400 da signaliziramo da je zahtjev nevažeći
+ return res.status(400).json({ message: "User already exists" });
+ }
- user = new User(req.body);
- await user.save();
+ user = new User(req.body);
+ await user.save();
- const token = jwt.sign(
- { userId: user.id },
- process.env.JWT_SECRET_KEY as string,
- {
- expiresIn: "1d",
+ // KREIRANJE I POSTAVLJANJE TOKENA
+ const { accessToken, refreshToken } = createTokens(user.id, user.role);
+
+ // 1. Postavljanje Access Tokena (auth_token)
+ res.cookie("auth_token", accessToken, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 15 * 60 * 1000, // 15 minuta
+ path: "/",
+ });
+
+ // 2. Postavljanje Refresh Tokena (refresh_token)
+ res.cookie("refresh_token", refreshToken, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 dana
+ path: "/",
+ });
+
+ return res.status(200).send({ message: "User registered OK", userId: user.id });
+ } catch (error) {
+ console.error("Error during user registration:", error);
+ // Vraćamo generički 500 error klijentu
+ res.status(500).send({ message: "Something went wrong on the server" });
}
- );
-
- res.cookie("auth_token", token, {
- httpOnly: true,
- secure: process.env.NODE_ENV === "production",
- sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
- maxAge: 86400000,
- path: "/",
- });
- return res.status(200).send({ message: "User registered OK" });
- } catch (error) {
- console.log(error);
- res.status(500).send({ message: "Something went wrong" });
}
- }
);
-export default router;
+
+router.get("/me", verifyToken, async (req: Request, res: Response) => {
+ // req.userId je dostupan zahvaljujući verifyToken middlewareu
+ const userId = req.userId;
+
+ try {
+ // Selektujemo sve osim lozinke (-password)
+ const user = await User.findById(userId).select("-password");
+ if (!user) {
+ // Ako korisnik ne postoji, vraćamo 404
+ return res.status(404).json({ message: "User not found" });
+ }
+ res.json(user);
+ } catch (error) {
+ console.error("Error fetching user data:", error);
+ res.status(500).json({ message: "Something went wrong on the server" });
+ }
+});
+
+export default router;
\ No newline at end of file
diff --git a/hotel-booking-backend/src/swagger.ts b/hotel-booking-backend/src/swagger.ts
index 25e19fb..e8ab25a 100644
--- a/hotel-booking-backend/src/swagger.ts
+++ b/hotel-booking-backend/src/swagger.ts
@@ -25,20 +25,20 @@ const options = {
],
components: {
securitySchemes: {
- cookieAuth: {
- type: "apiKey",
- in: "cookie",
- name: "jwt",
+ bearerAuth: {
+ type: "http",
+ scheme: "bearer",
+ bearerFormat: "JWT",
},
},
},
security: [
{
- cookieAuth: [],
+ bearerAuth: [],
},
],
},
- apis: ["./src/routes/*.ts"], // Path to the API routes
+ apis: ["./src/routes/*.ts"],
};
export const specs = swaggerJsdoc(options);
diff --git a/hotel-booking-backend/tsconfig.json b/hotel-booking-backend/tsconfig.json
index 96a177d..8e1c15b 100644
--- a/hotel-booking-backend/tsconfig.json
+++ b/hotel-booking-backend/tsconfig.json
@@ -80,6 +80,8 @@
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
/* Type Checking */
"strict": false /* Enable all strict type-checking options. */,
@@ -106,5 +108,6 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"noEmitOnError": false /* Disable emitting files if any type checking errors are reported. */
+
}
}
diff --git a/hotel-booking-frontend/package-lock.json b/hotel-booking-frontend/package-lock.json
index 395c408..2808a2f 100644
--- a/hotel-booking-frontend/package-lock.json
+++ b/hotel-booking-frontend/package-lock.json
@@ -21,6 +21,7 @@
"@types/recharts": "^1.8.29",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
+ "firebase-admin": "^13.6.0",
"js-cookie": "^3.0.5",
"lucide-react": "^0.542.0",
"react": "^18.2.0",
@@ -579,6 +580,113 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@fastify/busboy": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz",
+ "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==",
+ "license": "MIT"
+ },
+ "node_modules/@firebase/app-check-interop-types": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
+ "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app-types": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
+ "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/auth-interop-types": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
+ "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/component": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz",
+ "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/util": "1.13.0",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz",
+ "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.3",
+ "@firebase/auth-interop-types": "0.2.4",
+ "@firebase/component": "0.7.0",
+ "@firebase/logger": "0.5.0",
+ "@firebase/util": "1.13.0",
+ "faye-websocket": "0.11.4",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-compat": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz",
+ "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.0",
+ "@firebase/database": "1.1.0",
+ "@firebase/database-types": "1.0.16",
+ "@firebase/logger": "0.5.0",
+ "@firebase/util": "1.13.0",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-types": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz",
+ "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-types": "0.9.3",
+ "@firebase/util": "1.13.0"
+ }
+ },
+ "node_modules/@firebase/logger": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
+ "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/util": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz",
+ "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
@@ -617,6 +725,146 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
+ "node_modules/@google-cloud/firestore": {
+ "version": "7.11.6",
+ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz",
+ "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@opentelemetry/api": "^1.3.0",
+ "fast-deep-equal": "^3.1.1",
+ "functional-red-black-tree": "^1.0.1",
+ "google-gax": "^4.3.3",
+ "protobufjs": "^7.2.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/paginator": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
+ "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "arrify": "^2.0.0",
+ "extend": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/projectify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
+ "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/promisify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
+ "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@google-cloud/storage": {
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.3.tgz",
+ "integrity": "sha512-gOnCAbFgAYKRozywLsxagdevTF7Gm+2Ncz5u5CQAuOv/2VCa0rdGJWvJFDOftPx1tc+q8TXiC2pEJfFKu+yeMQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@google-cloud/paginator": "^5.0.0",
+ "@google-cloud/projectify": "^4.0.0",
+ "@google-cloud/promisify": "<4.1.0",
+ "abort-controller": "^3.0.0",
+ "async-retry": "^1.3.3",
+ "duplexify": "^4.1.3",
+ "fast-xml-parser": "^4.4.1",
+ "gaxios": "^6.0.2",
+ "google-auth-library": "^9.6.3",
+ "html-entities": "^2.5.2",
+ "mime": "^3.0.0",
+ "p-limit": "^3.0.1",
+ "retry-request": "^7.0.0",
+ "teeny-request": "^9.0.0",
+ "uuid": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@google-cloud/storage/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz",
+ "integrity": "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/proto-loader": "^0.8.0",
+ "@js-sdsl/ordered-map": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=12.10.0"
+ }
+ },
+ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
+ "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.5.3",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.15",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -698,6 +946,17 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@js-sdsl/ordered-map": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -733,6 +992,16 @@
"node": ">= 8"
}
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -742,6 +1011,80 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
@@ -1996,6 +2339,16 @@
"@swc/counter": "^0.1.3"
}
},
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -2006,6 +2359,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/caseless": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
+ "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
@@ -2137,6 +2497,23 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -2152,6 +2529,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
@@ -2219,6 +2602,37 @@
"@types/react": "*"
}
},
+ "node_modules/@types/request": {
+ "version": "2.48.13",
+ "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz",
+ "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/caseless": "*",
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "form-data": "^2.5.5"
+ }
+ },
+ "node_modules/@types/request/node_modules/form-data": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
+ "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.35",
+ "safe-buffer": "^5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
"node_modules/@types/scheduler": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz",
@@ -2251,6 +2665,13 @@
"@types/send": "*"
}
},
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
@@ -2466,6 +2887,19 @@
"vite": "^4 || ^5 || ^6 || ^7"
}
},
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@@ -2487,6 +2921,15 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2507,7 +2950,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=8"
}
@@ -2516,7 +2959,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -2579,6 +3022,26 @@
"node": ">=8"
}
},
+ "node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async-retry": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
+ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "retry": "0.13.1"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2638,6 +3101,26 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "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/big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -2646,6 +3129,15 @@
"node": ">=0.6"
}
},
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -2725,6 +3217,12 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/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",
@@ -2849,6 +3347,21 @@
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -2862,7 +3375,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -2874,7 +3387,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "devOptional": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -3074,7 +3587,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -3169,12 +3681,51 @@
"node": ">= 0.4"
}
},
+ "node_modules/duplexify": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
+ "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "end-of-stream": "^1.4.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1",
+ "stream-shift": "^1.0.2"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.582",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz",
"integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==",
"dev": true
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT",
+ "optional": true
+ },
+ "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",
+ "optional": true,
+ "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",
@@ -3276,7 +3827,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=6"
}
@@ -3456,17 +4007,41 @@
"node": ">=0.10.0"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/farmhash-modern": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz",
+ "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.3.2",
@@ -3508,6 +4083,25 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fast-xml-parser": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
+ "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "strnum": "^1.1.1"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -3517,6 +4111,18 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -3558,6 +4164,47 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/firebase-admin": {
+ "version": "13.6.0",
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.6.0.tgz",
+ "integrity": "sha512-GdPA/t0+Cq8p1JnjFRBmxRxAGvF/kl2yfdhALl38PrRp325YxyQ5aNaHui0XmaKcKiGRFIJ/EgBNWFoDP0onjw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@fastify/busboy": "^3.0.0",
+ "@firebase/database-compat": "^2.0.0",
+ "@firebase/database-types": "^1.0.6",
+ "@types/node": "^22.8.7",
+ "farmhash-modern": "^1.1.0",
+ "fast-deep-equal": "^3.1.1",
+ "google-auth-library": "^9.14.2",
+ "jsonwebtoken": "^9.0.0",
+ "jwks-rsa": "^3.1.0",
+ "node-forge": "^1.3.1",
+ "uuid": "^11.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@google-cloud/firestore": "^7.11.0",
+ "@google-cloud/storage": "^7.14.0"
+ }
+ },
+ "node_modules/firebase-admin/node_modules/@types/node": {
+ "version": "22.19.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
+ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/firebase-admin/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
"node_modules/flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
@@ -3654,6 +4301,66 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/gaxios": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "is-stream": "^2.0.0",
+ "node-fetch": "^2.6.9",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gaxios/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^6.1.1",
+ "google-logging-utils": "^0.0.2",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -3766,6 +4473,70 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/google-auth-library": {
+ "version": "9.15.1",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^6.1.1",
+ "gcp-metadata": "^6.1.0",
+ "gtoken": "^7.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-gax": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz",
+ "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/grpc-js": "^1.10.9",
+ "@grpc/proto-loader": "^0.7.13",
+ "@types/long": "^4.0.0",
+ "abort-controller": "^3.0.0",
+ "duplexify": "^4.0.0",
+ "google-auth-library": "^9.3.0",
+ "node-fetch": "^2.7.0",
+ "object-hash": "^3.0.0",
+ "proto3-json-serializer": "^2.0.2",
+ "protobufjs": "^7.3.2",
+ "retry-request": "^7.0.0",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-gax/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -3784,6 +4555,19 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "node_modules/gtoken": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
+ "license": "MIT",
+ "dependencies": {
+ "gaxios": "^6.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3832,6 +4616,70 @@
"node": ">= 0.4"
}
},
+ "node_modules/html-entities": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/http-parser-js": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
+ "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
+ "license": "MIT"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -3932,6 +4780,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -3963,6 +4821,18 @@
"node": ">=8"
}
},
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3978,6 +4848,15 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@@ -4009,6 +4888,15 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4027,6 +4915,111 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwks-rsa": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz",
+ "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "^4.17.20",
+ "@types/jsonwebtoken": "^9.0.4",
+ "debug": "^4.3.4",
+ "jose": "^4.15.4",
+ "limiter": "^1.1.5",
+ "lru-memoizer": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/jwks-rsa/node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": {
+ "version": "4.19.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
+ "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4058,26 +5051,80 @@
"node": ">=10"
}
},
+ "node_modules/limiter": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
+ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
+ },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -4085,6 +5132,19 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0",
+ "optional": true
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4100,7 +5160,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -4108,6 +5167,16 @@
"node": ">=10"
}
},
+ "node_modules/lru-memoizer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
+ "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.clonedeep": "^4.5.0",
+ "lru-cache": "6.0.0"
+ }
+ },
"node_modules/lucide-react": {
"version": "0.542.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz",
@@ -4163,6 +5232,19 @@
"resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
},
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -4198,8 +5280,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@@ -4245,6 +5326,35 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "license": "(BSD-3-Clause OR GPL-2.0)",
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@@ -4281,7 +5391,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">= 6"
}
@@ -4320,7 +5430,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -4587,6 +5697,44 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proto3-json-serializer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
+ "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "protobufjs": "^7.2.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -4878,6 +6026,21 @@
"pify": "^2.3.0"
}
},
+ "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",
+ "optional": true,
+ "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",
@@ -4937,6 +6100,16 @@
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
"integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@@ -4969,6 +6142,31 @@
"node": ">=4"
}
},
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/retry-request": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
+ "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/request": "^2.48.8",
+ "extend": "^3.0.2",
+ "teeny-request": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -5056,6 +6254,26 @@
"queue-microtask": "^1.2.2"
}
},
+ "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/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -5068,7 +6286,6 @@
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
- "dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -5119,11 +6336,53 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stream-events": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
+ "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "stubs": "^3.0.0"
+ }
+ },
+ "node_modules/stream-shift": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
+ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "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",
+ "optional": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -5143,6 +6402,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strnum": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/stubs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/sucrase": {
"version": "3.34.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
@@ -5256,6 +6535,64 @@
"node": ">=14.0.0"
}
},
+ "node_modules/teeny-request": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
+ "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "node-fetch": "^2.6.9",
+ "stream-events": "^1.0.5",
+ "uuid": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/teeny-request/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/teeny-request/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/teeny-request/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -5347,6 +6684,12 @@
"node": ">=8.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@@ -5518,7 +6861,20 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
+ "devOptional": true
+ },
+ "node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
},
"node_modules/victory-vendor": {
"version": "37.3.6",
@@ -5662,6 +7018,45 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5677,16 +7072,43 @@
"node": ">= 8"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.8.1",
@@ -5701,11 +7123,40 @@
"node": ">= 14.6"
}
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=10"
},
diff --git a/hotel-booking-frontend/package.json b/hotel-booking-frontend/package.json
index f46639c..8ef173a 100644
--- a/hotel-booking-frontend/package.json
+++ b/hotel-booking-frontend/package.json
@@ -23,6 +23,7 @@
"@types/recharts": "^1.8.29",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
+ "firebase-admin": "^13.6.0",
"js-cookie": "^3.0.5",
"lucide-react": "^0.542.0",
"react": "^18.2.0",
diff --git a/hotel-booking-frontend/src/App.tsx b/hotel-booking-frontend/src/App.tsx
index ffa648e..96bfae4 100644
--- a/hotel-booking-frontend/src/App.tsx
+++ b/hotel-booking-frontend/src/App.tsx
@@ -4,14 +4,15 @@ import {
Routes,
Navigate,
} from "react-router-dom";
+
import Layout from "./layouts/Layout";
import AuthLayout from "./layouts/AuthLayout";
import ScrollToTop from "./components/ScrollToTop";
import { Toaster } from "./components/ui/toaster";
+
import Register from "./pages/Register";
import SignIn from "./pages/SignIn";
import AddHotel from "./pages/AddHotel";
-import useAppContext from "./hooks/useAppContext";
import MyHotels from "./pages/MyHotels";
import EditHotel from "./pages/EditHotel";
import Search from "./pages/Search";
@@ -21,126 +22,118 @@ import MyBookings from "./pages/MyBookings";
import Home from "./pages/Home";
import ApiDocs from "./pages/ApiDocs";
import ApiStatus from "./pages/ApiStatus";
-import AnalyticsDashboard from "./pages/AnalyticsDashboard";
+import AdminDashboard from "./pages/AdminDashboard";
+import Unauthorized from "./pages/Unauthorized";
+
+import ProtectedRoute from "./components/ProtectedRoute";
const App = () => {
- const { isLoggedIn } = useAppContext();
return (
+
+ {/* PUBLIC ROUTES */}
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* AUTH ROUTES */}
-
-
+
+
+
}
/>
-
-
+
+
+
}
/>
+
+ {/* LOGGED-IN USERS */}
-
-
+
+
+
+
+
}
/>
+
-
-
+
+
+
+
+
}
/>
+
+ {/* HOTEL OWNER */}
-
-
+
+
+
+
+
}
/>
+
-
-
+
+
+
+
+
}
/>
+
-
-
+
+
+
+
+
}
/>
+
+ {/* ADMIN */}
-
-
+
+
+
+
+
}
/>
- {isLoggedIn && (
- <>
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
- >
- )}
- } />
+ {/* SYSTEM */}
+ } />
+
+ {/* FALLBACK */}
+ } />
+
);
diff --git a/hotel-booking-frontend/src/api-client.ts b/hotel-booking-frontend/src/api-client.ts
index 4bb79a6..487e7b6 100644
--- a/hotel-booking-frontend/src/api-client.ts
+++ b/hotel-booking-frontend/src/api-client.ts
@@ -10,12 +10,8 @@ import {
BookingType,
} from "../../shared/types";
import { BookingFormData } from "./forms/BookingForm/BookingForm";
-import { queryClient } from "./main";
-export const fetchCurrentUser = async (): Promise => {
- const response = await axiosInstance.get("/api/users/me");
- return response.data;
-};
+// --- AUTH I USER SEKCIJA ---
export const register = async (formData: RegisterFormData) => {
const response = await axiosInstance.post("/api/users/register", formData);
@@ -24,80 +20,34 @@ export const register = async (formData: RegisterFormData) => {
export const signIn = async (formData: SignInFormData) => {
const response = await axiosInstance.post("/api/auth/login", formData);
-
- // Store JWT token from response body in localStorage
- const token = response.data?.token;
- if (token) {
- localStorage.setItem("session_id", token);
- console.log("JWT token stored in localStorage for incognito compatibility");
- }
-
- // Store user info for incognito mode fallback
+
+ // Ako ti user_id treba za neku logiku koja nije vezana za auth (npr. analitika)
if (response.data?.userId) {
localStorage.setItem("user_id", response.data.userId);
- console.log("User ID stored for incognito mode fallback");
}
-
- // Force validate token after successful login to update React Query cache
- try {
- const validationResult = await validateToken();
- console.log("Token validation after login:", validationResult);
-
- // Invalidate and refetch the validateToken query to update the UI
- queryClient.invalidateQueries("validateToken");
-
- // Force a refetch to ensure the UI updates
- await queryClient.refetchQueries("validateToken");
- } catch (error) {
- console.log("Token validation failed after login, but continuing...");
-
- // Even if validation fails, if we have a token stored, consider it a success for incognito mode
- if (localStorage.getItem("session_id")) {
- console.log("Incognito mode detected - using stored token as fallback");
- }
- }
-
+
return response.data;
};
export const validateToken = async () => {
- try {
- const response = await axiosInstance.get("/api/auth/validate-token");
- return response.data;
- } catch (error: any) {
- if (error.response?.status === 401) {
- // Not logged in, throw error so React Query knows it failed
- throw new Error("Token invalid");
- }
- // For any other error (network, etc.), also throw
- throw new Error("Token validation failed");
- }
+ // Axios interceptor će ovdje automatski uraditi refresh ako je auth_token istekao
+ const response = await axiosInstance.get("/api/auth/validate-token");
+ return response.data;
};
export const signOut = async () => {
const response = await axiosInstance.post("/api/auth/logout");
-
- // Clear localStorage (JWT tokens)
- localStorage.removeItem("session_id");
localStorage.removeItem("user_id");
-
return response.data;
};
-// Development utility to clear all browser storage
-export const clearAllStorage = () => {
- // Clear localStorage
- localStorage.clear();
- // Clear sessionStorage
- sessionStorage.clear();
- // Clear cookies (by setting them to expire in the past)
- document.cookie.split(";").forEach((c) => {
- document.cookie = c
- .replace(/^ +/, "")
- .replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
- });
+export const fetchCurrentUser = async (): Promise => {
+ const response = await axiosInstance.get("/api/users/me");
+ return response.data;
};
+// --- MOJI HOTELI (ADMIN/HOST) ---
+
export const addMyHotel = async (hotelFormData: FormData) => {
const response = await axiosInstance.post("/api/my-hotels", hotelFormData, {
headers: {
@@ -131,6 +81,8 @@ export const updateMyHotelById = async (hotelFormData: FormData) => {
return response.data;
};
+// --- PRETRAGA I JAVNI HOTELI ---
+
export type SearchParams = {
destination?: string;
checkIn?: string;
@@ -150,8 +102,7 @@ export const searchHotels = async (
): Promise => {
const queryParams = new URLSearchParams();
- // Only add destination if it's not empty
- if (searchParams.destination && searchParams.destination.trim() !== "") {
+ if (searchParams.destination?.trim()) {
queryParams.append("destination", searchParams.destination.trim());
}
@@ -163,10 +114,7 @@ export const searchHotels = async (
queryParams.append("maxPrice", searchParams.maxPrice || "");
queryParams.append("sortOption", searchParams.sortOption || "");
- searchParams.facilities?.forEach((facility) =>
- queryParams.append("facilities", facility)
- );
-
+ searchParams.facilities?.forEach((facility) => queryParams.append("facilities", facility));
searchParams.types?.forEach((type) => queryParams.append("types", type));
searchParams.stars?.forEach((star) => queryParams.append("stars", star));
@@ -184,6 +132,8 @@ export const fetchHotelById = async (hotelId: string): Promise => {
return response.data;
};
+// --- REZERVACIJE I PLAĆANJE ---
+
export const createPaymentIntent = async (
hotelId: string,
numberOfNights: string
@@ -208,14 +158,62 @@ export const fetchMyBookings = async (): Promise => {
return response.data;
};
-export const fetchHotelBookings = async (
- hotelId: string
-): Promise => {
+export const fetchHotelBookings = async (hotelId: string): Promise => {
const response = await axiosInstance.get(`/api/bookings/hotel/${hotelId}`);
return response.data;
};
-// Business Insights API functions
+// --- RECENZIJE I FAVORITI ---
+
+export const fetchReviewsByHotelId = async (hotelId: string) => {
+ try {
+ const response = await axiosInstance.get(`/api/reviews/${hotelId}`);
+ return response.data;
+ } catch (error: any) {
+ if (error.response?.status === 404) return [];
+ throw new Error("Failed to fetch reviews");
+ }
+};
+
+export type CreateReviewPayload = {
+ rating: number;
+ comment: string;
+ categories: {
+ cleanliness: number;
+ service: number;
+ location: number;
+ value: number;
+ amenities: number;
+ };
+};
+
+export const createReview = async (hotelId: string, payload: CreateReviewPayload) => {
+ const response = await axiosInstance.post(`/api/reviews/${hotelId}`, payload);
+ return response.data;
+};
+
+export const fetchFavorites = async (): Promise<{ hotelId: string }[]> => {
+ const response = await axiosInstance.get("/api/favorites");
+ return response.data;
+};
+
+export const addFavorite = async (hotelId: string) => {
+ const response = await axiosInstance.post(`/api/favorites/${hotelId}`, {});
+ return response.data;
+};
+
+export const removeFavorite = async (hotelId: string) => {
+ const response = await axiosInstance.delete(`/api/favorites/${hotelId}`);
+ return response.data;
+};
+
+// --- ADMIN I INSIGHTS ---
+
+export const fetchAdminDashboard = async () => {
+ const response = await axiosInstance.get("/api/admin/dashboard");
+ return response.data;
+};
+
export const fetchBusinessInsightsDashboard = async () => {
const response = await axiosInstance.get("/api/business-insights/dashboard");
return response.data;
@@ -227,8 +225,12 @@ export const fetchBusinessInsightsForecast = async () => {
};
export const fetchBusinessInsightsPerformance = async () => {
- const response = await axiosInstance.get(
- "/api/business-insights/performance"
- );
+ const response = await axiosInstance.get("/api/business-insights/performance");
return response.data;
};
+
+// Pomocna funkcija za čišćenje (samo za dev)
+export const clearAllStorage = () => {
+ localStorage.clear();
+ sessionStorage.clear();
+};
\ No newline at end of file
diff --git a/hotel-booking-frontend/src/components/AdvancedSearch.tsx b/hotel-booking-frontend/src/components/AdvancedSearch.tsx
index be082bf..c516fff 100644
--- a/hotel-booking-frontend/src/components/AdvancedSearch.tsx
+++ b/hotel-booking-frontend/src/components/AdvancedSearch.tsx
@@ -77,9 +77,7 @@ const AdvancedSearch: React.FC = ({
}
}
- const apiBaseUrl =
- import.meta.env.VITE_API_BASE_URL || "http://localhost:7002";
- const response = await fetch(`${apiBaseUrl}/api/hotels`);
+ const response = await fetch("/api/hotels");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@@ -112,7 +110,7 @@ const AdvancedSearch: React.FC = ({
};
fetchPlaces();
- }, []); // Remove all dependencies to run only once on mount
+ }, []);
// Clear dropdown state when component mounts
useEffect(() => {
@@ -198,48 +196,28 @@ const AdvancedSearch: React.FC = ({
// Add advanced filters
if (searchData.minPrice)
searchParams.append("minPrice", searchData.minPrice);
- if (searchData.maxPrice)
- searchParams.append("maxPrice", searchData.maxPrice);
+ if (searchData.maxPrice !== "" && !isNaN(Number(searchData.maxPrice))) {
+ searchParams.append("maxPrice", searchData.maxPrice);
+ }
if (searchData.starRating)
searchParams.append("starRating", searchData.starRating);
if (searchData.hotelType)
searchParams.append("hotelType", searchData.hotelType);
- if (searchData.sortBy) searchParams.append("sortBy", searchData.sortBy);
+ if (searchData.sortBy)
+ searchParams.append("sortOption", searchData.sortBy);
if (searchData.radius) searchParams.append("radius", searchData.radius);
searchData.facilities.forEach((facility) =>
searchParams.append("facilities", facility)
);
+ if (searchData.checkOut <= searchData.checkIn) {
+ alert("Check-out date must be after check-in date.");
+ return;
+ }
+ // Normal navigation
navigate(`/search?${searchParams.toString()}`);
onSearch(searchData);
- // Don't clear search values immediately - let the search page use them
- // Only clear the local form state
- setTimeout(() => {
- setSearchData({
- destination: "",
- checkIn: new Date(),
- checkOut: new Date(),
- adultCount: 1,
- childCount: 0,
- minPrice: "",
- maxPrice: "",
- starRating: "",
- hotelType: "",
- facilities: [],
- sortBy: "relevance",
- radius: "50",
- instantBooking: false,
- freeCancellation: false,
- breakfast: false,
- wifi: false,
- parking: false,
- pool: false,
- gym: false,
- spa: false,
- });
- // Remove this line: search.clearSearchValues();
- }, 100);
return;
}
@@ -267,13 +245,14 @@ const AdvancedSearch: React.FC = ({
// Add advanced filters
if (searchData.minPrice)
searchParams.append("minPrice", searchData.minPrice);
- if (searchData.maxPrice)
- searchParams.append("maxPrice", searchData.maxPrice);
+ if (searchData.maxPrice !== "" && !isNaN(Number(searchData.maxPrice))) {
+ searchParams.append("maxPrice", searchData.maxPrice);
+ }
if (searchData.starRating)
searchParams.append("starRating", searchData.starRating);
if (searchData.hotelType)
searchParams.append("hotelType", searchData.hotelType);
- if (searchData.sortBy) searchParams.append("sortBy", searchData.sortBy);
+ if (searchData.sortBy) searchParams.append("sortOption", searchData.sortBy);
if (searchData.radius) searchParams.append("radius", searchData.radius);
searchData.facilities.forEach((facility) =>
searchParams.append("facilities", facility)
@@ -281,34 +260,6 @@ const AdvancedSearch: React.FC = ({
navigate(`/search?${searchParams.toString()}`);
onSearch(searchData);
-
- // Don't clear search values immediately - let the search page use them
- // Only clear the local form state
- setTimeout(() => {
- setSearchData({
- destination: "",
- checkIn: new Date(),
- checkOut: new Date(),
- adultCount: 1,
- childCount: 0,
- minPrice: "",
- maxPrice: "",
- starRating: "",
- hotelType: "",
- facilities: [],
- sortBy: "relevance",
- radius: "50",
- instantBooking: false,
- freeCancellation: false,
- breakfast: false,
- wifi: false,
- parking: false,
- pool: false,
- gym: false,
- spa: false,
- });
- // Remove this line: search.clearSearchValues();
- }, 100);
};
const handleQuickSearch = (destination: string) => {
@@ -323,32 +274,6 @@ const AdvancedSearch: React.FC = ({
setTimeout(() => handleSearch(), 100);
};
- // const handleClear = () => {
- // setSearchData({
- // destination: "",
- // checkIn: new Date(),
- // checkOut: new Date(),
- // adultCount: 1,
- // childCount: 0,
- // minPrice: "",
- // maxPrice: "",
- // starRating: "",
- // hotelType: "",
- // facilities: [],
- // sortBy: "relevance",
- // radius: "50",
- // instantBooking: false,
- // freeCancellation: false,
- // breakfast: false,
- // wifi: false,
- // parking: false,
- // pool: false,
- // gym: false,
- // spa: false,
- // });
- // search.clearSearchValues();
- // };
-
const popularDestinations = [
"New York",
"London",
@@ -360,6 +285,8 @@ const AdvancedSearch: React.FC = ({
"Barcelona",
];
+ const toDateInputValue = (d: Date) => d.toISOString().split("T")[0];
+
return (
@@ -429,10 +367,18 @@ const AdvancedSearch: React.FC = ({
- handleInputChange("checkOut", new Date(e.target.value))
- }
+ value={toDateInputValue(searchData.checkOut)}
+ min={toDateInputValue(searchData.checkIn)}
+ onChange={(e) => {
+ const newCheckOut = new Date(e.target.value);
+
+ if (newCheckOut <= searchData.checkIn) {
+ alert("Check-out date must be after check-in date.");
+ return;
+ }
+
+ handleInputChange("checkOut", newCheckOut);
+ }}
/>
diff --git a/hotel-booking-frontend/src/components/BookingLogModal.tsx b/hotel-booking-frontend/src/components/BookingLogModal.tsx
index 6cec168..09d03aa 100644
--- a/hotel-booking-frontend/src/components/BookingLogModal.tsx
+++ b/hotel-booking-frontend/src/components/BookingLogModal.tsx
@@ -4,7 +4,6 @@ import * as apiClient from "../api-client";
import { BookingType } from "../../../shared/types";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog";
import { Badge } from "./ui/badge";
-// import { Button } from "./ui/button";
import { Card, CardContent, CardHeader } from "./ui/card";
import {
Calendar,
@@ -16,7 +15,6 @@ import {
Star,
CreditCard,
FileText,
- // X,
Filter,
} from "lucide-react";
@@ -154,14 +152,6 @@ const BookingLogModal: React.FC = ({
- {/* */}
diff --git a/hotel-booking-frontend/src/components/Footer.tsx b/hotel-booking-frontend/src/components/Footer.tsx
index b6eab54..3f93b2d 100644
--- a/hotel-booking-frontend/src/components/Footer.tsx
+++ b/hotel-booking-frontend/src/components/Footer.tsx
@@ -1,3 +1,4 @@
+import { Link } from "react-router-dom";
import {
Building2,
Mail,
@@ -26,140 +27,146 @@ const Footer = () => {
Discover amazing hotels, resorts, and accommodations worldwide.
Book with confidence and enjoy unforgettable experiences.
+
+ {/* Socials (external) */}
- {/* Quick Links */}
+ {/* Quick Links (internal) */}
- {/* Support */}
+ {/* Support (internal) */}
- {/* Contact Info */}
+ {/* Contact Info (mailto/tel) */}
Contact Us
-
-
+
+1 (555) 123-4567
+
+
@@ -176,24 +183,24 @@ const Footer = () => {
© 2025 MernHolidays. All rights reserved.
diff --git a/hotel-booking-frontend/src/components/Header.tsx b/hotel-booking-frontend/src/components/Header.tsx
index 27bb49c..51d3c7c 100644
--- a/hotel-booking-frontend/src/components/Header.tsx
+++ b/hotel-booking-frontend/src/components/Header.tsx
@@ -2,34 +2,21 @@ import { Link, useNavigate } from "react-router-dom";
import useAppContext from "../hooks/useAppContext";
import useSearchContext from "../hooks/useSearchContext";
import SignOutButton from "./SignOutButton";
-import {
- FileText,
- Activity,
- BarChart3,
- Building2,
- Calendar,
- LogIn,
-} from "lucide-react";
+import { BarChart3, Building2, Calendar, LogIn } from "lucide-react";
const Header = () => {
- const { isLoggedIn } = useAppContext();
+ const { isLoggedIn, user } = useAppContext();
+ const role = user?.role;
const search = useSearchContext();
const navigate = useNavigate();
const handleLogoClick = () => {
- // Clear search context when going to home page
search.clearSearchValues();
navigate("/");
};
return (
<>
- {/* Development Banner */}
- {/* {!import.meta.env.PROD && (
-
- 🚧 Development Mode - Auth state persists between sessions
-
- )} */}
@@ -50,16 +37,16 @@ const Header = () => {