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 (
{/* Basic Search */} @@ -410,10 +337,21 @@ const AdvancedSearch: React.FC = ({ - handleInputChange("checkIn", new Date(e.target.value)) - } + value={toDateInputValue(searchData.checkIn)} + onChange={(e) => { + const newCheckIn = new Date(e.target.value); + + setSearchData((prev) => { + const adjustedCheckOut = + prev.checkOut && prev.checkOut < newCheckIn ? newCheckIn : prev.checkOut; + + return { + ...prev, + checkIn: newCheckIn, + checkOut: adjustedCheckOut, + }; + }); + }} />
@@ -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

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 = () => {