diff --git a/.gcloudignore b/.gcloudignore new file mode 100644 index 0000000..5a616cb --- /dev/null +++ b/.gcloudignore @@ -0,0 +1,17 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +node_modules +#!include:.gitignore diff --git a/backend/.firebaserc b/backend/.firebaserc new file mode 100644 index 0000000..4227ca1 --- /dev/null +++ b/backend/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "cim-summarizer" + } +} \ No newline at end of file diff --git a/backend/.gcloudignore b/backend/.gcloudignore new file mode 100644 index 0000000..19315ab --- /dev/null +++ b/backend/.gcloudignore @@ -0,0 +1,76 @@ +# This file specifies files that are intentionally untracked by Git. +# Files matching these patterns will not be uploaded to Cloud Functions + +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +.next/ +out/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +logs/ +*.log +firebase-debug.log +firebase-debug.*.log + +# Test files +coverage/ +.nyc_output +*.lcov + +# Upload files and temporary data +uploads/ +temp/ +tmp/ + +# Documentation and markdown files +*.md +AGENTIC_RAG_DATABASE_INTEGRATION.md +DATABASE.md +HYBRID_IMPLEMENTATION_SUMMARY.md +RAG_PROCESSING_README.md +go-forward-fixes-summary.md + +# Scripts and setup files +*.sh +setup-env.sh +fix-env-config.sh + +# Database files +*.sql +supabase_setup.sql + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Jest configuration +jest.config.js + +# TypeScript config (we only need the transpiled JS) +tsconfig.json \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..1443840 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,57 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +.next/ +out/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env.development +.env.production + +# Logs +logs/ +*.log +firebase-debug.log +firebase-debug.*.log + +# Test files +coverage/ +.nyc_output +*.lcov + +# Upload files and temporary data +uploads/ +temp/ +tmp/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Firebase +.firebase/ +firebase-debug.log* +firebase-debug.*.log* \ No newline at end of file diff --git a/backend/deploy.sh b/backend/deploy.sh new file mode 100755 index 0000000..afc3795 --- /dev/null +++ b/backend/deploy.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +echo "Building TypeScript..." +npm run build + +echo "Deploying function to Firebase..." +gcloud functions deploy api \ + --gen2 \ + --runtime nodejs20 \ + --region us-central1 \ + --source . \ + --entry-point api \ + --trigger-http \ + --allow-unauthenticated \ No newline at end of file diff --git a/backend/firebase.json b/backend/firebase.json new file mode 100644 index 0000000..f4cf4f5 --- /dev/null +++ b/backend/firebase.json @@ -0,0 +1,8 @@ +{ + "functions": { + "source": ".", + "runtime": "nodejs20", + "ignore": ["node_modules"], + "predeploy": "npm run build" + } +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 5ed7a90..c33c5e8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,21 +9,18 @@ "version": "1.0.0", "dependencies": { "@anthropic-ai/sdk": "^0.57.0", - "@langchain/openai": "^0.6.3", + "@supabase/supabase-js": "^2.53.0", "axios": "^1.11.0", - "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", - "bull": "^4.12.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "express-rate-limit": "^7.1.5", - "express-validator": "^7.0.1", - "form-data": "^4.0.4", + "firebase-admin": "^13.4.0", + "firebase-functions": "^6.4.0", "helmet": "^7.1.0", "joi": "^17.11.0", "jsonwebtoken": "^9.0.2", - "langchain": "^0.3.30", "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", "openai": "^5.10.2", @@ -595,13 +592,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@cfworker/json-schema": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", - "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", - "license": "MIT", - "peer": true - }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -733,6 +723,247 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "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.3", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.3.tgz", + "integrity": "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w==", + "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.16.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", + "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "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.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "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/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -810,12 +1041,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", - "license": "MIT" - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1271,180 +1496,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@langchain/core": { - "version": "0.3.66", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.66.tgz", - "integrity": "sha512-d3SgSDOlgOjdIbReIXVQl9HaQzKqO/5+E+o3kJwoKXLGP9dxi7+lMyaII7yv7G8/aUxMWLwFES9zc1jFoeJEZw==", + "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", - "peer": true, - "dependencies": { - "@cfworker/json-schema": "^4.0.2", - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.3.46", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.25.32", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, + "optional": true, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" } }, - "node_modules/@langchain/core/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@langchain/core/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@langchain/openai": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.6.3.tgz", - "integrity": "sha512-dSNuXDTJitDzN8D2wFNqWVELDbBRhMpJiFeiWpHjfPuq7R6wSjzNNY/Uk6x+FLpvbOs/zKNWy5+0q0p3KrCjRQ==", - "license": "MIT", - "dependencies": { - "js-tiktoken": "^1.0.12", - "openai": "^5.3.0", - "zod": "^3.25.32" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.58 <0.4.0" - } - }, - "node_modules/@langchain/textsplitters": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", - "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", - "license": "MIT", - "dependencies": { - "js-tiktoken": "^1.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.2.21 <0.4.0" - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -1496,6 +1558,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/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -1506,6 +1578,70 @@ "@noble/hashes": "^1.1.5" } }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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", + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, "node_modules/@puppeteer/browsers": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", @@ -1663,6 +1799,91 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.71.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz", + "integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz", + "integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.15", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.15.tgz", + "integrity": "sha512-HQKRnwAqdVqJW/P9TjKVK+/ETpW4yQ8tyDPPtRMKOH4Uh3vQD74vmj353CYs8+YwVBKubeUOOEpI9CT8mT4obw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "isows": "^1.0.7", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.10.4.tgz", + "integrity": "sha512-cvL02GarJVFcNoWe36VBybQqTVRq6wQSOCvTS64C+eyuxOruFIm1utZAY0xi2qKtHJO3EjKaj8iWJKySusDmAQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.53.0.tgz", + "integrity": "sha512-Vg9sl0oFn55cCPaEOsDsRDbxOVccxRrK/cikjL1XbywHEOfyA5SOOEypidMvQLwgoAfnC2S4D9BQwJDcZs7/TQ==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.71.1", + "@supabase/functions-js": "2.4.5", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.15", + "@supabase/storage-js": "^2.10.4" + } + }, + "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/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -1753,18 +1974,23 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, + "node_modules/@types/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/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1781,7 +2007,6 @@ "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1791,7 +2016,6 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -1804,7 +2028,6 @@ "version": "4.19.6", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -1827,7 +2050,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -1879,13 +2101,19 @@ "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, "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/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -1897,7 +2125,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/morgan": { @@ -1914,7 +2141,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, "license": "MIT" }, "node_modules/@types/multer": { @@ -1931,7 +2157,6 @@ "version": "20.19.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1959,25 +2184,54 @@ "pg-types": "^2.2.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" + "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/semver": { "version": "7.7.0", @@ -1990,7 +2244,6 @@ "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -2001,7 +2254,6 @@ "version": "1.15.8", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -2053,6 +2305,13 @@ "@types/superagent": "*" } }, + "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/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -2063,8 +2322,18 @@ "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/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -2297,6 +2566,19 @@ "dev": true, "license": "ISC" }, + "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", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2474,6 +2756,16 @@ "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/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -2499,6 +2791,16 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "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", @@ -2709,26 +3011,21 @@ "node": ">=10.0.0" } }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, + "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.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2905,33 +3202,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/bull": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/bull/-/bull-4.16.5.tgz", - "integrity": "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==", - "license": "MIT", - "dependencies": { - "cron-parser": "^4.9.0", - "get-port": "^5.1.1", - "ioredis": "^5.3.2", - "lodash": "^4.17.21", - "msgpackr": "^1.11.2", - "semver": "^7.5.2", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/bull/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", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3025,6 +3295,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3269,15 +3540,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/console-table-printer": { - "version": "2.14.6", - "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", - "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", - "license": "MIT", - "dependencies": { - "simple-wcswidth": "^1.0.1" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3402,18 +3664,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -3464,16 +3714,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -3529,15 +3769,6 @@ "node": ">=0.4.0" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3557,16 +3788,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3666,6 +3887,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/dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", @@ -4064,11 +4313,15 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" + "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/execa": { "version": "5.1.1", @@ -4181,19 +4434,6 @@ "express": ">= 4.11" } }, - "node_modules/express-validator": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", - "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21", - "validator": "~13.12.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4209,6 +4449,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "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/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -4244,11 +4490,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "devOptional": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -4308,6 +4563,25 @@ "dev": true, "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/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4318,6 +4592,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/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -4442,6 +4728,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase-admin": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.4.0.tgz", + "integrity": "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ==", + "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", + "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.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.0.tgz", + "integrity": "sha512-bbAKTCqX5aNVryi7qXVMi+OkB3w/OyblodicMbvE38blyAz7GxXf6XYhklokijuPwwVg9sDLKRxt0ZHXQwZVfQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/firebase-functions": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.4.0.tgz", + "integrity": "sha512-Q/LGhJrmJEhT0dbV60J4hCkVSeOM6/r7xJS/ccmkXzTWMjo+UPAYX9zlQmGlEjotstZ0U9GtQSJSgbB2Z+TJDg==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.21", + "cors": "^2.8.5", + "express": "^4.21.0", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -4571,6 +4913,56 @@ "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/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -4633,18 +5025,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4781,6 +5161,91 @@ "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-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", @@ -4807,10 +5272,45 @@ "dev": true, "license": "MIT" }, + "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": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4864,6 +5364,23 @@ "node": ">=16.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/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4887,6 +5404,12 @@ "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": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5029,30 +5552,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ioredis": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", - "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -5197,6 +5696,21 @@ "dev": true, "license": "ISC" }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -5907,13 +6421,13 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/js-tiktoken": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", - "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "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", - "dependencies": { - "base64-js": "^1.5.1" + "funding": { + "url": "https://github.com/sponsors/panva" } }, "node_modules/js-tokens": { @@ -5953,6 +6467,15 @@ "node": ">=6" } }, + "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", @@ -5993,15 +6516,6 @@ "node": ">=6" } }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -6035,6 +6549,23 @@ "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/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -6071,162 +6602,6 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, - "node_modules/langchain": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.30.tgz", - "integrity": "sha512-UyVsfwHDpHbrnWrjWuhJHqi8Non+Zcsf2kdpDTqyJF8NXrHBOpjdHT5LvPuW9fnE7miDTWf5mLcrWAGZgcrznQ==", - "license": "MIT", - "dependencies": { - "@langchain/openai": ">=0.1.0 <0.7.0", - "@langchain/textsplitters": ">=0.0.0 <0.2.0", - "js-tiktoken": "^1.0.12", - "js-yaml": "^4.1.0", - "jsonpointer": "^5.0.1", - "langsmith": "^0.3.33", - "openapi-types": "^12.1.3", - "p-retry": "4", - "uuid": "^10.0.0", - "yaml": "^2.2.1", - "zod": "^3.25.32" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/anthropic": "*", - "@langchain/aws": "*", - "@langchain/cerebras": "*", - "@langchain/cohere": "*", - "@langchain/core": ">=0.3.58 <0.4.0", - "@langchain/deepseek": "*", - "@langchain/google-genai": "*", - "@langchain/google-vertexai": "*", - "@langchain/google-vertexai-web": "*", - "@langchain/groq": "*", - "@langchain/mistralai": "*", - "@langchain/ollama": "*", - "@langchain/xai": "*", - "axios": "*", - "cheerio": "*", - "handlebars": "^4.7.8", - "peggy": "^3.0.2", - "typeorm": "*" - }, - "peerDependenciesMeta": { - "@langchain/anthropic": { - "optional": true - }, - "@langchain/aws": { - "optional": true - }, - "@langchain/cerebras": { - "optional": true - }, - "@langchain/cohere": { - "optional": true - }, - "@langchain/deepseek": { - "optional": true - }, - "@langchain/google-genai": { - "optional": true - }, - "@langchain/google-vertexai": { - "optional": true - }, - "@langchain/google-vertexai-web": { - "optional": true - }, - "@langchain/groq": { - "optional": true - }, - "@langchain/mistralai": { - "optional": true - }, - "@langchain/ollama": { - "optional": true - }, - "@langchain/xai": { - "optional": true - }, - "axios": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "handlebars": { - "optional": true - }, - "peggy": { - "optional": true - }, - "typeorm": { - "optional": true - } - } - }, - "node_modules/langchain/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/langsmith": { - "version": "0.3.49", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.49.tgz", - "integrity": "sha512-hVLpGzTDq4dFffScKuF9yIuwXqp6LJCsvxK4UjmLae+oEodfnFIQ6yVmNyhxFnm3QuRl1NY8qLFul3k+R1YnGQ==", - "license": "MIT", - "dependencies": { - "@types/uuid": "^10.0.0", - "chalk": "^4.1.2", - "console-table-printer": "^2.12.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "@opentelemetry/api": "*", - "@opentelemetry/exporter-trace-otlp-proto": "*", - "@opentelemetry/sdk-trace-base": "*", - "openai": "*" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@opentelemetry/exporter-trace-otlp-proto": { - "optional": true - }, - "@opentelemetry/sdk-trace-base": { - "optional": true - }, - "openai": { - "optional": true - } - } - }, - "node_modules/langsmith/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6251,6 +6626,11 @@ "node": ">= 0.8.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/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6273,16 +6653,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "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.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "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": { @@ -6291,12 +6672,6 @@ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6364,6 +6739,12 @@ "node": ">= 12.0.0" } }, + "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" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6374,15 +6755,34 @@ "yallist": "^3.0.2" } }, - "node_modules/luxon": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", - "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", + "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", - "engines": { - "node": ">=12" + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" } }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -6624,37 +7024,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, "node_modules/multer": { "version": "1.4.5-lts.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", @@ -6674,16 +7043,6 @@ "node": ">= 6.0.0" } }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "license": "MIT", - "peer": true, - "bin": { - "mustache": "bin/mustache" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6709,15 +7068,6 @@ "node": ">= 0.4.0" } }, - "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/node-ensure": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", @@ -6744,30 +7094,13 @@ } } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "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-int64": { @@ -6816,6 +7149,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", @@ -6904,12 +7247,6 @@ } } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6928,20 +7265,11 @@ "node": ">= 0.8.0" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "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==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6969,47 +7297,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -7465,6 +7752,43 @@ "node": ">= 6" } }, + "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.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "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", @@ -7747,27 +8071,6 @@ "@redis/time-series": "1.1.0" } }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7845,10 +8148,26 @@ "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.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -8134,12 +8453,6 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, - "node_modules/simple-wcswidth": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", - "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", - "license": "MIT" - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8263,12 +8576,6 @@ "node": ">=8" } }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8278,6 +8585,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", @@ -8387,6 +8711,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/superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -8442,6 +8786,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8485,6 +8830,79 @@ "streamx": "^2.15.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/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/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/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -8913,7 +9331,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/unpipe": { @@ -9022,15 +9439,6 @@ "node": ">=10.12.0" } }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9056,6 +9464,29 @@ "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", @@ -9198,8 +9629,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -9241,18 +9670,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -9304,7 +9721,7 @@ "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, "license": "MIT", "engines": { "node": ">=10" @@ -9321,16 +9738,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peer": true, - "peerDependencies": { - "zod": "^3.24.1" - } } } } diff --git a/backend/package.json b/backend/package.json index e97fd7a..980e868 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,12 +17,15 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.57.0", + "@supabase/supabase-js": "^2.53.0", "axios": "^1.11.0", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "express-rate-limit": "^7.1.5", + "firebase-admin": "^13.4.0", + "firebase-functions": "^6.4.0", "helmet": "^7.1.0", "joi": "^17.11.0", "jsonwebtoken": "^9.0.2", diff --git a/backend/src/config/env.ts b/backend/src/config/env.ts index 665127c..3a884f4 100644 --- a/backend/src/config/env.ts +++ b/backend/src/config/env.ts @@ -9,23 +9,28 @@ const envSchema = Joi.object({ NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'), PORT: Joi.number().default(5000), - // Database - DATABASE_URL: Joi.string().required(), + // Database - Made optional for Firebase deployment with Supabase + DATABASE_URL: Joi.string().allow('').default(''), DB_HOST: Joi.string().default('localhost'), DB_PORT: Joi.number().default(5432), - DB_NAME: Joi.string().required(), - DB_USER: Joi.string().required(), - DB_PASSWORD: Joi.string().required(), + DB_NAME: Joi.string().allow('').default(''), + DB_USER: Joi.string().allow('').default(''), + DB_PASSWORD: Joi.string().allow('').default(''), + + // Supabase Configuration + SUPABASE_URL: Joi.string().allow('').optional(), + SUPABASE_ANON_KEY: Joi.string().allow('').optional(), + SUPABASE_SERVICE_KEY: Joi.string().allow('').optional(), // Redis REDIS_URL: Joi.string().default('redis://localhost:6379'), REDIS_HOST: Joi.string().default('localhost'), REDIS_PORT: Joi.number().default(6379), - // JWT - JWT_SECRET: Joi.string().required(), + // JWT - Optional for Firebase Auth + JWT_SECRET: Joi.string().default('default-jwt-secret-change-in-production'), JWT_EXPIRES_IN: Joi.string().default('1h'), - JWT_REFRESH_SECRET: Joi.string().required(), + JWT_REFRESH_SECRET: Joi.string().default('default-refresh-secret-change-in-production'), JWT_REFRESH_EXPIRES_IN: Joi.string().default('7d'), // File Upload @@ -137,6 +142,12 @@ export const config = { password: envVars.DB_PASSWORD, }, + supabase: { + url: envVars.SUPABASE_URL, + anonKey: envVars.SUPABASE_ANON_KEY, + serviceKey: envVars.SUPABASE_SERVICE_KEY, + }, + redis: { url: envVars.REDIS_URL, host: envVars.REDIS_HOST, @@ -260,7 +271,7 @@ export const config = { // Vector Database Configuration vector: { - provider: envVars['VECTOR_PROVIDER'] || 'pgvector', // 'pinecone' | 'pgvector' | 'chroma' + provider: envVars['VECTOR_PROVIDER'] || 'supabase', // 'pinecone' | 'pgvector' | 'chroma' | 'supabase' // Pinecone Configuration pineconeApiKey: envVars['PINECONE_API_KEY'], diff --git a/backend/src/config/errorConfig.ts b/backend/src/config/errorConfig.ts new file mode 100644 index 0000000..53b9d93 --- /dev/null +++ b/backend/src/config/errorConfig.ts @@ -0,0 +1,47 @@ +export const errorConfig = { + // Authentication timeouts + auth: { + tokenRefreshInterval: 45 * 60 * 1000, // 45 minutes + sessionTimeout: 60 * 60 * 1000, // 1 hour + maxRetryAttempts: 3, + }, + + // Upload timeouts + upload: { + maxUploadTime: 300000, // 5 minutes + maxFileSize: 100 * 1024 * 1024, // 100MB + progressCheckInterval: 2000, // 2 seconds + }, + + // Processing timeouts + processing: { + maxProcessingTime: 1800000, // 30 minutes + progressUpdateInterval: 5000, // 5 seconds + maxRetries: 3, + }, + + // Network timeouts + network: { + requestTimeout: 30000, // 30 seconds + retryDelay: 1000, // 1 second + maxRetries: 3, + }, + + // Error messages + messages: { + tokenExpired: 'Your session has expired. Please log in again.', + uploadFailed: 'File upload failed. Please try again.', + processingFailed: 'Document processing failed. Please try again.', + networkError: 'Network error. Please check your connection and try again.', + unauthorized: 'You are not authorized to perform this action.', + serverError: 'Server error. Please try again later.', + }, + + // Logging levels + logging: { + auth: 'info', + upload: 'info', + processing: 'info', + error: 'error', + }, +}; \ No newline at end of file diff --git a/backend/src/config/firebase.ts b/backend/src/config/firebase.ts new file mode 100644 index 0000000..44c8cf9 --- /dev/null +++ b/backend/src/config/firebase.ts @@ -0,0 +1,49 @@ +import admin from 'firebase-admin'; + +// Initialize Firebase Admin SDK +if (!admin.apps.length) { + try { + // Check if we're running in Firebase Functions environment + const isCloudFunction = process.env['FUNCTION_TARGET'] || process.env['FUNCTIONS_EMULATOR']; + + if (isCloudFunction) { + // In Firebase Functions, use default initialization + admin.initializeApp({ + projectId: process.env['GCLOUD_PROJECT'] || 'cim-summarizer', + }); + console.log('Firebase Admin SDK initialized for Cloud Functions'); + } else { + // For local development, try to use service account key if available + try { + const serviceAccount = require('../../serviceAccountKey.json'); + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + projectId: 'cim-summarizer', + }); + console.log('Firebase Admin SDK initialized with service account'); + } catch (serviceAccountError) { + // Fallback to default initialization + admin.initializeApp({ + projectId: 'cim-summarizer', + }); + console.log('Firebase Admin SDK initialized with default credentials'); + } + } + + console.log('Firebase apps count:', admin.apps.length); + console.log('Project ID:', admin.app().options.projectId); + } catch (error) { + console.error('Failed to initialize Firebase Admin SDK:', error); + + // Final fallback: try with minimal config + try { + admin.initializeApp(); + console.log('Firebase Admin SDK initialized with minimal fallback'); + } catch (fallbackError) { + console.error('All Firebase initialization attempts failed:', fallbackError); + // Don't throw here to prevent the entire app from crashing + } + } +} + +export default admin; \ No newline at end of file diff --git a/backend/src/config/supabase.ts b/backend/src/config/supabase.ts new file mode 100644 index 0000000..4ea52fe --- /dev/null +++ b/backend/src/config/supabase.ts @@ -0,0 +1,56 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { config } from './env'; +import { logger } from '../utils/logger'; + +let supabase: SupabaseClient | null = null; + +export const getSupabaseClient = (): SupabaseClient => { + if (!supabase) { + const supabaseUrl = config.supabase?.url; + const supabaseKey = config.supabase?.anonKey; + + if (!supabaseUrl || !supabaseKey) { + logger.warn('Supabase credentials not configured, some features may not work'); + throw new Error('Supabase configuration missing'); + } + + supabase = createClient(supabaseUrl, supabaseKey); + logger.info('Supabase client initialized'); + } + + return supabase; +}; + +export const getSupabaseServiceClient = (): SupabaseClient => { + const supabaseUrl = config.supabase?.url; + const supabaseServiceKey = config.supabase?.serviceKey; + + if (!supabaseUrl || !supabaseServiceKey) { + logger.warn('Supabase service credentials not configured'); + throw new Error('Supabase service configuration missing'); + } + + return createClient(supabaseUrl, supabaseServiceKey); +}; + +// Test connection function +export const testSupabaseConnection = async (): Promise => { + try { + const client = getSupabaseClient(); + const { error } = await client.from('_health_check').select('*').limit(1); + + // If the table doesn't exist, that's fine - we just tested the connection + if (error && !error.message.includes('relation "_health_check" does not exist')) { + logger.error('Supabase connection test failed:', error); + return false; + } + + logger.info('Supabase connection test successful'); + return true; + } catch (error) { + logger.error('Supabase connection test failed:', error); + return false; + } +}; + +export default getSupabaseClient; \ No newline at end of file diff --git a/backend/src/controllers/authController.ts b/backend/src/controllers/authController.ts index 86b16eb..46a2f21 100644 --- a/backend/src/controllers/authController.ts +++ b/backend/src/controllers/authController.ts @@ -1,14 +1,5 @@ import { Request, Response } from 'express'; import { AuthenticatedRequest } from '../middleware/auth'; -import { UserModel } from '../models/UserModel'; -import { - generateAuthTokens, - verifyRefreshToken, - hashPassword, - comparePassword, - validatePassword -} from '../utils/auth'; -import { sessionService } from '../services/sessionService'; import logger from '../utils/logger'; export interface RegisterRequest extends Request { @@ -33,432 +24,106 @@ export interface RefreshTokenRequest extends Request { } /** - * Register a new user + * DEPRECATED: Legacy auth controller + * All auth functions are now handled by Firebase Auth */ -export async function register(req: RegisterRequest, res: Response): Promise { - try { - const { email, name, password } = req.body; - - // Validate input - if (!email || !name || !password) { - res.status(400).json({ - success: false, - message: 'Email, name, and password are required' - }); - return; - } - - // Validate email format - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - res.status(400).json({ - success: false, - message: 'Invalid email format' - }); - return; - } - - // Validate password strength - const passwordValidation = validatePassword(password); - if (!passwordValidation.isValid) { - res.status(400).json({ - success: false, - message: 'Password does not meet requirements', - errors: passwordValidation.errors - }); - return; - } - - // Check if user already exists - const existingUser = await UserModel.findByEmail(email); - if (existingUser) { - res.status(409).json({ - success: false, - message: 'User with this email already exists' - }); - return; - } - - // Hash password - const hashedPassword = await hashPassword(password); - - // Create user - const user = await UserModel.create({ - email, - name, - password: hashedPassword, - role: 'user' - }); - - // Generate tokens - const tokens = generateAuthTokens({ - userId: user.id, - email: user.email, - role: user.role - }); - - // Store session - await sessionService.storeSession(user.id, { - userId: user.id, - email: user.email, - role: user.role, - refreshToken: tokens.refreshToken - }); - - logger.info(`New user registered: ${email}`); - - res.status(201).json({ - success: true, - message: 'User registered successfully', - data: { - user: { - id: user.id, - email: user.email, - name: user.name, - role: user.role - }, - tokens: { - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - expiresIn: tokens.expiresIn - } - } - }); - } catch (error) { - logger.error('Registration error:', error); - res.status(500).json({ +export const authController = { + async register(_req: RegisterRequest, res: Response): Promise { + logger.warn('Legacy register endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ success: false, - message: 'Internal server error during registration' + message: 'Legacy registration is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async login(_req: LoginRequest, res: Response): Promise { + logger.warn('Legacy login endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy login is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async refreshToken(_req: RefreshTokenRequest, res: Response): Promise { + logger.warn('Legacy refresh token endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy token refresh is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async logout(_req: AuthenticatedRequest, res: Response): Promise { + logger.warn('Legacy logout endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy logout is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async getProfile(_req: AuthenticatedRequest, res: Response): Promise { + logger.warn('Legacy profile endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy profile access is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async updateProfile(_req: AuthenticatedRequest, res: Response): Promise { + logger.warn('Legacy profile update endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy profile updates are disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async changePassword(_req: AuthenticatedRequest, res: Response): Promise { + logger.warn('Legacy password change endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy password changes are disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async deleteAccount(_req: AuthenticatedRequest, res: Response): Promise { + logger.warn('Legacy account deletion endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy account deletion is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async verifyEmail(_req: Request, res: Response): Promise { + logger.warn('Legacy email verification endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy email verification is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async requestPasswordReset(_req: Request, res: Response): Promise { + logger.warn('Legacy password reset endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy password reset is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' + }); + }, + + async resetPassword(_req: Request, res: Response): Promise { + logger.warn('Legacy password reset endpoint is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy password reset is disabled. Use Firebase Auth instead.', + error: 'DEPRECATED_ENDPOINT' }); } -} - -/** - * Login user - */ -export async function login(req: LoginRequest, res: Response): Promise { - try { - const { email, password } = req.body; - - // Validate input - if (!email || !password) { - res.status(400).json({ - success: false, - message: 'Email and password are required' - }); - return; - } - - // Find user by email - const user = await UserModel.findByEmail(email); - if (!user) { - res.status(401).json({ - success: false, - message: 'Invalid email or password' - }); - return; - } - - // Check if user is active - if (!user.is_active) { - res.status(401).json({ - success: false, - message: 'Account is deactivated' - }); - return; - } - - // Verify password - const isPasswordValid = await comparePassword(password, user.password_hash); - if (!isPasswordValid) { - res.status(401).json({ - success: false, - message: 'Invalid email or password' - }); - return; - } - - // Generate tokens - const tokens = generateAuthTokens({ - userId: user.id, - email: user.email, - role: user.role - }); - - // Store session - await sessionService.storeSession(user.id, { - userId: user.id, - email: user.email, - role: user.role, - refreshToken: tokens.refreshToken - }); - - // Update last login - await UserModel.updateLastLogin(user.id); - - logger.info(`User logged in: ${email}`); - - res.status(200).json({ - success: true, - message: 'Login successful', - data: { - user: { - id: user.id, - email: user.email, - name: user.name, - role: user.role - }, - tokens: { - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - expiresIn: tokens.expiresIn - } - } - }); - } catch (error) { - logger.error('Login error:', error); - res.status(500).json({ - success: false, - message: 'Internal server error during login' - }); - } -} - -/** - * Logout user - */ -export async function logout(req: AuthenticatedRequest, res: Response): Promise { - try { - if (!req.user) { - res.status(401).json({ - success: false, - message: 'Authentication required' - }); - return; - } - - // Get the token from header for blacklisting - const authHeader = req.headers.authorization; - if (authHeader) { - const token = authHeader.split(' ')[1]; - if (token) { - // Blacklist the access token - await sessionService.blacklistToken(token, 3600); // 1 hour - } - } - - // Remove session - await sessionService.removeSession(req.user.id); - - logger.info(`User logged out: ${req.user.email}`); - - res.status(200).json({ - success: true, - message: 'Logout successful' - }); - } catch (error) { - logger.error('Logout error:', error); - res.status(500).json({ - success: false, - message: 'Internal server error during logout' - }); - } -} - -/** - * Refresh access token - */ -export async function refreshToken(req: RefreshTokenRequest, res: Response): Promise { - try { - const { refreshToken } = req.body; - - if (!refreshToken) { - res.status(400).json({ - success: false, - message: 'Refresh token is required' - }); - return; - } - - // Verify refresh token - const decoded = verifyRefreshToken(refreshToken); - - // Check if user exists and is active - const user = await UserModel.findById(decoded.userId); - if (!user || !user.is_active) { - res.status(401).json({ - success: false, - message: 'Invalid refresh token' - }); - return; - } - - // Check if session exists and matches - const session = await sessionService.getSession(decoded.userId); - if (!session || session.refreshToken !== refreshToken) { - res.status(401).json({ - success: false, - message: 'Invalid refresh token' - }); - return; - } - - // Generate new tokens - const tokens = generateAuthTokens({ - userId: user.id, - email: user.email, - role: user.role - }); - - // Update session with new refresh token - await sessionService.storeSession(user.id, { - userId: user.id, - email: user.email, - role: user.role, - refreshToken: tokens.refreshToken - }); - - // Blacklist old refresh token - await sessionService.blacklistToken(refreshToken, 86400); // 24 hours - - logger.info(`Token refreshed for user: ${user.email}`); - - res.status(200).json({ - success: true, - message: 'Token refreshed successfully', - data: { - tokens: { - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - expiresIn: tokens.expiresIn - } - } - }); - } catch (error) { - logger.error('Token refresh error:', error); - res.status(401).json({ - success: false, - message: 'Invalid refresh token' - }); - } -} - -/** - * Get current user profile - */ -export async function getProfile(req: AuthenticatedRequest, res: Response): Promise { - try { - if (!req.user) { - res.status(401).json({ - success: false, - message: 'Authentication required' - }); - return; - } - - const user = await UserModel.findById(req.user.id); - if (!user) { - res.status(404).json({ - success: false, - message: 'User not found' - }); - return; - } - - res.status(200).json({ - success: true, - data: { - user: { - id: user.id, - email: user.email, - name: user.name, - role: user.role, - created_at: user.created_at, - last_login: user.last_login - } - } - }); - } catch (error) { - logger.error('Get profile error:', error); - res.status(500).json({ - success: false, - message: 'Internal server error' - }); - } -} - -/** - * Update user profile - */ -export async function updateProfile(req: AuthenticatedRequest, res: Response): Promise { - try { - if (!req.user) { - res.status(401).json({ - success: false, - message: 'Authentication required' - }); - return; - } - - const { name, email } = req.body; - - // Validate input - if (email) { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - res.status(400).json({ - success: false, - message: 'Invalid email format' - }); - return; - } - - // Check if email is already taken by another user - const existingUser = await UserModel.findByEmail(email); - if (existingUser && existingUser.id !== req.user.id) { - res.status(409).json({ - success: false, - message: 'Email is already taken' - }); - return; - } - } - - // Update user - const updatedUser = await UserModel.update(req.user.id, { - name: name || undefined, - email: email || undefined - }); - - if (!updatedUser) { - res.status(404).json({ - success: false, - message: 'User not found' - }); - return; - } - - logger.info(`Profile updated for user: ${req.user.email}`); - - res.status(200).json({ - success: true, - message: 'Profile updated successfully', - data: { - user: { - id: updatedUser.id, - email: updatedUser.email, - name: updatedUser.name, - role: updatedUser.role, - created_at: updatedUser.created_at, - last_login: updatedUser.last_login - } - } - }); - } catch (error) { - logger.error('Update profile error:', error); - res.status(500).json({ - success: false, - message: 'Internal server error' - }); - } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/backend/src/controllers/documentController.ts b/backend/src/controllers/documentController.ts index 857df67..d7f1c49 100644 --- a/backend/src/controllers/documentController.ts +++ b/backend/src/controllers/documentController.ts @@ -8,7 +8,7 @@ import { uploadProgressService } from '../services/uploadProgressService'; export const documentController = { async uploadDocument(req: Request, res: Response): Promise { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { res.status(401).json({ error: 'User not authenticated' }); return; @@ -85,7 +85,7 @@ export const documentController = { async getDocuments(req: Request, res: Response): Promise { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { res.status(401).json({ error: 'User not authenticated' }); return; @@ -116,7 +116,7 @@ export const documentController = { async getDocument(req: Request, res: Response): Promise { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { res.status(401).json({ error: 'User not authenticated' }); return; @@ -164,7 +164,7 @@ export const documentController = { async getDocumentProgress(req: Request, res: Response): Promise { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { res.status(401).json({ error: 'User not authenticated' }); return; @@ -219,7 +219,7 @@ export const documentController = { async deleteDocument(req: Request, res: Response): Promise { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { res.status(401).json({ error: 'User not authenticated' }); return; diff --git a/backend/src/index.ts b/backend/src/index.ts index 50b4d4a..5196d80 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,3 +1,6 @@ +// Initialize Firebase Admin SDK first +import './config/firebase'; + import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; @@ -5,15 +8,17 @@ import morgan from 'morgan'; import rateLimit from 'express-rate-limit'; import { config } from './config/env'; import { logger } from './utils/logger'; -import authRoutes from './routes/auth'; import documentRoutes from './routes/documents'; import vectorRoutes from './routes/vector'; + import { errorHandler } from './middleware/errorHandler'; import { notFoundHandler } from './middleware/notFoundHandler'; -import { jobQueueService } from './services/jobQueueService'; + const app = express(); -const PORT = config.port || 5000; + +// Enable trust proxy to ensure Express works correctly behind the proxy +app.set('trust proxy', 1); // Security middleware app.use(helmet({ @@ -28,11 +33,27 @@ app.use(helmet({ })); // CORS configuration +const allowedOrigins = [ + 'https://cim-summarizer.web.app', + 'https://cim-summarizer.firebaseapp.com', + 'http://localhost:3000', + 'http://localhost:5173' +]; + app.use(cors({ - origin: config.frontendUrl || 'http://localhost:3000', + origin: function (origin, callback) { + console.log('CORS request from origin:', origin); + if (!origin || allowedOrigins.indexOf(origin) !== -1) { + callback(null, true); + } else { + console.log('CORS blocked origin:', origin); + callback(new Error('Not allowed by CORS')); + } + }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], + optionsSuccessStatus: 200 })); // Rate limiting @@ -97,19 +118,21 @@ app.get('/health/agentic-rag/metrics', async (_req, res) => { } }); -// API routes -app.use('/api/auth', authRoutes); -app.use('/api/documents', documentRoutes); -app.use('/api/vector', vectorRoutes); +// API routes - remove the /api prefix as it's handled by Firebase +app.use('/documents', documentRoutes); +app.use('/vector', vectorRoutes); + + +import * as functions from 'firebase-functions'; // API root endpoint -app.get('/api', (_req, res) => { // _req to fix TS6133 +app.get('/', (_req, res) => { // _req to fix TS6133 res.json({ message: 'CIM Document Processor API', version: '1.0.0', endpoints: { - auth: '/api/auth', - documents: '/api/documents', + auth: '/auth', + documents: '/documents', health: '/health', agenticRagHealth: '/health/agentic-rag', agenticRagMetrics: '/health/agentic-rag/metrics', @@ -123,51 +146,18 @@ app.use(notFoundHandler); // Global error handler (must be last) app.use(errorHandler); -// Start server -const server = app.listen(PORT, () => { - logger.info(`🚀 Server running on port ${PORT}`); - logger.info(`📊 Environment: ${config.nodeEnv}`); - logger.info(`🔗 API URL: http://localhost:${PORT}/api`); - logger.info(`🏥 Health check: http://localhost:${PORT}/health`); -}); +// Initialize job queue service for document processing +import { jobQueueService } from './services/jobQueueService'; -// Start job queue service -jobQueueService.start(); -logger.info('📋 Job queue service started'); +// Start the job queue service asynchronously to avoid blocking function startup +// Use a longer delay to ensure the function is fully initialized +setTimeout(() => { + try { + jobQueueService.start(); + logger.info('Job queue service started successfully'); + } catch (error) { + logger.error('Failed to start job queue service', { error }); + } +}, 5000); -// Graceful shutdown -const gracefulShutdown = (signal: string) => { - logger.info(`${signal} received, shutting down gracefully`); - - // Stop accepting new connections - server.close(async () => { - logger.info('HTTP server closed'); - - // Stop job queue service - jobQueueService.stop(); - logger.info('Job queue service stopped'); - - // Stop upload progress service - try { - const { uploadProgressService } = await import('./services/uploadProgressService'); - uploadProgressService.stop(); - logger.info('Upload progress service stopped'); - } catch (error) { - logger.warn('Could not stop upload progress service', { error }); - } - - logger.info('Process terminated'); - process.exit(0); - }); - - // Force close after 30 seconds - setTimeout(() => { - logger.error('Could not close connections in time, forcefully shutting down'); - process.exit(1); - }, 30000); -}; - -process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); -process.on('SIGINT', () => gracefulShutdown('SIGINT')); - -export default app; \ No newline at end of file +export const api = functions.https.onRequest(app); \ No newline at end of file diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts index 0da5c44..b181157 100644 --- a/backend/src/middleware/auth.ts +++ b/backend/src/middleware/auth.ts @@ -1,244 +1,107 @@ import { Request, Response, NextFunction } from 'express'; -import { verifyAccessToken, extractTokenFromHeader } from '../utils/auth'; -import { sessionService } from '../services/sessionService'; -import { UserModel } from '../models/UserModel'; import logger from '../utils/logger'; export interface AuthenticatedRequest extends Request { - user?: { - id: string; - email: string; - role: string; - }; + user?: import('firebase-admin').auth.DecodedIdToken; } /** - * Authentication middleware to verify JWT tokens + * DEPRECATED: Legacy authentication middleware + * Use Firebase Auth instead via ../middleware/firebaseAuth */ export async function authenticateToken( - req: AuthenticatedRequest, + _req: AuthenticatedRequest, res: Response, - next: NextFunction + _next: NextFunction ): Promise { - try { - const authHeader = req.headers.authorization; - const token = extractTokenFromHeader(authHeader); - - if (!token) { - res.status(401).json({ - success: false, - message: 'Access token is required' - }); - return; - } - - // Check if token is blacklisted - const isBlacklisted = await sessionService.isTokenBlacklisted(token); - if (isBlacklisted) { - res.status(401).json({ - success: false, - message: 'Token has been revoked' - }); - return; - } - - // Verify the token - const decoded = verifyAccessToken(token); - - // Check if user still exists and is active - const user = await UserModel.findById(decoded.userId); - if (!user || !user.is_active) { - res.status(401).json({ - success: false, - message: 'User account is inactive or does not exist' - }); - return; - } - - // Check if session exists - const session = await sessionService.getSession(decoded.userId); - if (!session) { - res.status(401).json({ - success: false, - message: 'Session expired, please login again' - }); - return; - } - - // Attach user info to request - req.user = { - id: decoded.userId, - email: decoded.email, - role: decoded.role - }; - - logger.info(`Authenticated request for user: ${decoded.email}`); - next(); - } catch (error) { - logger.error('Authentication error:', error); - res.status(401).json({ - success: false, - message: 'Invalid or expired token' - }); - } + logger.warn('Legacy auth middleware is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy authentication is disabled. Use Firebase Auth instead.' + }); } // Alias for backward compatibility export const auth = authenticateToken; /** - * Role-based authorization middleware + * DEPRECATED: Role-based authorization middleware */ -export function requireRole(allowedRoles: string[]) { - return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => { - if (!req.user) { - res.status(401).json({ - success: false, - message: 'Authentication required' - }); - return; - } - - if (!allowedRoles.includes(req.user.role)) { - res.status(403).json({ - success: false, - message: 'Insufficient permissions' - }); - return; - } - - logger.info(`Authorized request for user: ${req.user.email} with role: ${req.user.role}`); - next(); +export function requireRole(_allowedRoles: string[]) { + return (_req: AuthenticatedRequest, res: Response, _next: NextFunction): void => { + logger.warn('Legacy role-based auth is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy role-based authentication is disabled. Use Firebase Auth instead.' + }); }; } /** - * Admin-only middleware + * DEPRECATED: Admin-only middleware */ export function requireAdmin( - req: AuthenticatedRequest, + _req: AuthenticatedRequest, res: Response, - next: NextFunction + _next: NextFunction ): void { - requireRole(['admin'])(req, res, next); + logger.warn('Legacy admin auth is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy admin authentication is disabled. Use Firebase Auth instead.' + }); } /** - * User or admin middleware + * DEPRECATED: User or admin middleware */ export function requireUserOrAdmin( - req: AuthenticatedRequest, + _req: AuthenticatedRequest, res: Response, - next: NextFunction + _next: NextFunction ): void { - requireRole(['user', 'admin'])(req, res, next); + logger.warn('Legacy user/admin auth is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy user/admin authentication is disabled. Use Firebase Auth instead.' + }); } /** - * Optional authentication middleware (doesn't fail if no token) + * DEPRECATED: Optional authentication middleware */ export async function optionalAuth( - req: AuthenticatedRequest, + _req: AuthenticatedRequest, _res: Response, next: NextFunction ): Promise { - try { - const authHeader = req.headers.authorization; - const token = extractTokenFromHeader(authHeader); - - if (!token) { - // No token provided, continue without authentication - next(); - return; - } - - // Check if token is blacklisted - const isBlacklisted = await sessionService.isTokenBlacklisted(token); - if (isBlacklisted) { - // Token is blacklisted, continue without authentication - next(); - return; - } - - // Verify the token - const decoded = verifyAccessToken(token); - - // Check if user still exists and is active - const user = await UserModel.findById(decoded.userId); - if (!user || !user.is_active) { - // User doesn't exist or is inactive, continue without authentication - next(); - return; - } - - // Check if session exists - const session = await sessionService.getSession(decoded.userId); - if (!session) { - // Session doesn't exist, continue without authentication - next(); - return; - } - - // Attach user info to request - req.user = { - id: decoded.userId, - email: decoded.email, - role: decoded.role - }; - - logger.info(`Optional authentication successful for user: ${decoded.email}`); - next(); - } catch (error) { - // Token verification failed, continue without authentication - logger.debug('Optional authentication failed, continuing without user context'); - next(); - } + logger.debug('Legacy optional auth is deprecated. Use Firebase Auth instead.'); + // For optional auth, we just continue without authentication + next(); } /** - * Rate limiting middleware for authentication endpoints + * DEPRECATED: Rate limiting middleware */ export function authRateLimit( _req: Request, _res: Response, next: NextFunction ): void { - // This would typically integrate with a rate limiting library - // For now, we'll just pass through - // TODO: Implement proper rate limiting next(); } /** - * Logout middleware to invalidate session + * DEPRECATED: Logout middleware */ export async function logout( - req: AuthenticatedRequest, + _req: AuthenticatedRequest, res: Response, - next: NextFunction + _next: NextFunction ): Promise { - try { - if (!req.user) { - res.status(401).json({ - success: false, - message: 'Authentication required' - }); - return; - } - - // Remove session - await sessionService.removeSession(req.user.id); - - // Update last login in database - await UserModel.updateLastLogin(req.user.id); - - logger.info(`User logged out: ${req.user.email}`); - next(); - } catch (error) { - logger.error('Logout error:', error); - res.status(500).json({ - success: false, - message: 'Error during logout' - }); - } -} \ No newline at end of file + logger.warn('Legacy logout is deprecated. Use Firebase Auth instead.'); + res.status(501).json({ + success: false, + message: 'Legacy logout is disabled. Use Firebase Auth instead.' + }); +} \ No newline at end of file diff --git a/backend/src/middleware/firebaseAuth.ts b/backend/src/middleware/firebaseAuth.ts new file mode 100644 index 0000000..be2ddca --- /dev/null +++ b/backend/src/middleware/firebaseAuth.ts @@ -0,0 +1,116 @@ +import { Request, Response, NextFunction } from 'express'; +import admin from 'firebase-admin'; +import { logger } from '../utils/logger'; + +// Initialize Firebase Admin if not already initialized +if (!admin.apps.length) { + admin.initializeApp(); +} + +export interface FirebaseAuthenticatedRequest extends Request { + user?: admin.auth.DecodedIdToken; +} + +export const verifyFirebaseToken = async ( + req: FirebaseAuthenticatedRequest, + res: Response, + next: NextFunction +): Promise => { + try { + // Debug Firebase Admin initialization + console.log('Firebase apps available:', admin.apps.length); + console.log('Firebase app names:', admin.apps.filter(app => app !== null).map(app => app!.name)); + + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + res.status(401).json({ error: 'No valid authorization header' }); + return; + } + + const idToken = authHeader.split('Bearer ')[1]; + + if (!idToken) { + res.status(401).json({ error: 'No token provided' }); + return; + } + + // Verify the Firebase ID token + const decodedToken = await admin.auth().verifyIdToken(idToken, true); + + // Check if token is expired + const now = Math.floor(Date.now() / 1000); + if (decodedToken.exp && decodedToken.exp < now) { + logger.warn('Token expired for user:', decodedToken.uid); + res.status(401).json({ error: 'Token expired' }); + return; + } + + req.user = decodedToken; + + // Log successful authentication + logger.info('Authenticated request for user:', decodedToken.email); + + next(); + } catch (error: any) { + logger.error('Firebase token verification failed:', { + error: error.message, + code: error.code, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + + // Try to recover from session if Firebase auth fails + try { + const authHeader = req.headers.authorization; + if (authHeader && authHeader.startsWith('Bearer ')) { + const idToken = authHeader.split('Bearer ')[1]; + + if (idToken) { + // Try to verify without force refresh + const decodedToken = await admin.auth().verifyIdToken(idToken, false); + req.user = decodedToken; + logger.info('Recovered authentication from session for user:', decodedToken.email); + next(); + return; + } + } + } catch (recoveryError) { + logger.debug('Session recovery failed:', recoveryError); + } + + // Provide more specific error messages + if (error.code === 'auth/id-token-expired') { + res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' }); + } else if (error.code === 'auth/id-token-revoked') { + res.status(401).json({ error: 'Token revoked', code: 'TOKEN_REVOKED' }); + } else if (error.code === 'auth/invalid-id-token') { + res.status(401).json({ error: 'Invalid token', code: 'INVALID_TOKEN' }); + } else { + res.status(401).json({ error: 'Invalid token' }); + } + } +}; + +export const optionalFirebaseAuth = async ( + req: FirebaseAuthenticatedRequest, + _res: Response, + next: NextFunction +): Promise => { + try { + const authHeader = req.headers.authorization; + + if (authHeader && authHeader.startsWith('Bearer ')) { + const idToken = authHeader.split('Bearer ')[1]; + if (idToken) { + const decodedToken = await admin.auth().verifyIdToken(idToken, true); + req.user = decodedToken; + } + } + } catch (error) { + // Silently ignore auth errors for optional auth + logger.debug('Optional auth failed:', error); + } + + next(); +}; \ No newline at end of file diff --git a/backend/src/middleware/upload.ts b/backend/src/middleware/upload.ts index d75031b..5ea891b 100644 --- a/backend/src/middleware/upload.ts +++ b/backend/src/middleware/upload.ts @@ -141,8 +141,30 @@ export const handleUploadError = (error: any, req: Request, res: Response, next: next(); }; -// Main upload middleware -export const uploadMiddleware = upload.single('document'); +// Main upload middleware with timeout handling +export const uploadMiddleware = (req: Request, res: Response, next: NextFunction) => { + // Set a timeout for the upload + const uploadTimeout = setTimeout(() => { + logger.error('Upload timeout for request:', { + ip: req.ip, + userAgent: req.get('User-Agent'), + }); + res.status(408).json({ + success: false, + error: 'Upload timeout', + message: 'Upload took too long to complete', + }); + }, 300000); // 5 minutes timeout + + // Clear timeout on successful upload + const originalNext = next; + next = (err?: any) => { + clearTimeout(uploadTimeout); + originalNext(err); + }; + + upload.single('document')(req, res, next); +}; // Combined middleware for file uploads export const handleFileUpload = [ diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts deleted file mode 100644 index ef443c1..0000000 --- a/backend/src/routes/auth.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Router } from 'express'; -import { - register, - login, - logout, - refreshToken, - getProfile, - updateProfile -} from '../controllers/authController'; -import { - authenticateToken, - authRateLimit -} from '../middleware/auth'; - -const router = Router(); - -/** - * @route POST /api/auth/register - * @desc Register a new user - * @access Public - */ -router.post('/register', authRateLimit, register); - -/** - * @route POST /api/auth/login - * @desc Login user - * @access Public - */ -router.post('/login', authRateLimit, login); - -/** - * @route POST /api/auth/logout - * @desc Logout user - * @access Private - */ -router.post('/logout', authenticateToken, logout); - -/** - * @route POST /api/auth/refresh - * @desc Refresh access token - * @access Public - */ -router.post('/refresh', authRateLimit, refreshToken); - -/** - * @route GET /api/auth/profile - * @desc Get current user profile - * @access Private - */ -router.get('/profile', authenticateToken, getProfile); - -/** - * @route PUT /api/auth/profile - * @desc Update current user profile - * @access Private - */ -router.put('/profile', authenticateToken, updateProfile); - -export default router; \ No newline at end of file diff --git a/backend/src/routes/documents.ts b/backend/src/routes/documents.ts index a98bd5b..a018938 100644 --- a/backend/src/routes/documents.ts +++ b/backend/src/routes/documents.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { authenticateToken } from '../middleware/auth'; +import { verifyFirebaseToken } from '../middleware/firebaseAuth'; import { documentController } from '../controllers/documentController'; import { unifiedDocumentProcessor } from '../services/unifiedDocumentProcessor'; import { logger } from '../utils/logger'; @@ -11,11 +11,7 @@ import { DocumentModel } from '../models/DocumentModel'; declare global { namespace Express { interface Request { - user?: { - id: string; - email: string; - role: string; - }; + user?: import('firebase-admin').auth.DecodedIdToken; } } } @@ -23,7 +19,7 @@ declare global { const router = express.Router(); // Apply authentication to all routes -router.use(authenticateToken); +router.use(verifyFirebaseToken); // Essential document management routes (keeping these) router.post('/upload', handleFileUpload, documentController.uploadDocument); @@ -36,7 +32,7 @@ router.delete('/:id', documentController.deleteDocument); // Analytics endpoints (keeping these for monitoring) router.get('/analytics', async (req, res) => { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); } @@ -67,7 +63,7 @@ router.get('/processing-stats', async (_req, res) => { // Download endpoint (keeping this) router.get('/:id/download', async (req, res) => { try { - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); } @@ -106,7 +102,7 @@ router.get('/:id/download', async (req, res) => { router.post('/:id/process-optimized-agentic-rag', async (req, res) => { try { const { id } = req.params; - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); @@ -147,7 +143,7 @@ router.post('/:id/process-optimized-agentic-rag', async (req, res) => { router.get('/:id/agentic-rag-sessions', async (req, res) => { try { const { id } = req.params; - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); @@ -183,7 +179,7 @@ router.get('/:id/agentic-rag-sessions', async (req, res) => { router.get('/agentic-rag-sessions/:sessionId', async (req, res) => { try { const { sessionId } = req.params; - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); @@ -245,7 +241,7 @@ router.get('/agentic-rag-sessions/:sessionId', async (req, res) => { router.get('/:id/analytics', async (req, res) => { try { const { id } = req.params; - const userId = req.user?.id; + const userId = req.user?.uid; if (!userId) { return res.status(401).json({ error: 'User not authenticated' }); diff --git a/backend/src/routes/vector.ts b/backend/src/routes/vector.ts index 887af80..126a162 100644 --- a/backend/src/routes/vector.ts +++ b/backend/src/routes/vector.ts @@ -90,7 +90,7 @@ router.get('/document-chunks/:documentId', async (req, res) => { */ router.get('/analytics', async (req, res) => { try { - const userId = req.user?.id; + const userId = req.user?.uid; const { days = 30 } = req.query; if (!userId) { diff --git a/backend/src/services/vectorDatabaseService.ts b/backend/src/services/vectorDatabaseService.ts index 7f74ec2..becdf63 100644 --- a/backend/src/services/vectorDatabaseService.ts +++ b/backend/src/services/vectorDatabaseService.ts @@ -1,23 +1,24 @@ import { config } from '../config/env'; import { logger } from '../utils/logger'; import { VectorDatabaseModel, DocumentChunk, VectorSearchResult } from '../models/VectorDatabaseModel'; -import pool from '../config/database'; // Re-export types from the model export { VectorSearchResult, DocumentChunk } from '../models/VectorDatabaseModel'; class VectorDatabaseService { - private provider: 'pinecone' | 'pgvector' | 'chroma'; + private provider: 'pinecone' | 'pgvector' | 'chroma' | 'supabase'; private client: any; private semanticCache: Map = new Map(); private readonly CACHE_TTL = 3600000; // 1 hour cache TTL constructor() { this.provider = config.vector.provider; - this.initializeClient(); + // Don't initialize client immediately - do it lazily when needed } private async initializeClient() { + if (this.client) return; // Already initialized + switch (this.provider) { case 'pinecone': await this.initializePinecone(); @@ -28,11 +29,22 @@ class VectorDatabaseService { case 'chroma': await this.initializeChroma(); break; + case 'supabase': + await this.initializeSupabase(); + break; default: - throw new Error(`Unsupported vector database provider: ${this.provider}`); + logger.error(`Unsupported vector database provider: ${this.provider}`); + this.client = null; } } + private async ensureInitialized() { + if (!this.client) { + await this.initializeClient(); + } + return this.client !== null; + } + private async initializePinecone() { // const { Pinecone } = await import('@pinecone-database/pinecone'); // this.client = new Pinecone({ @@ -42,42 +54,12 @@ class VectorDatabaseService { } private async initializePgVector() { - // Use imported database pool - this.client = pool; - - // Ensure pgvector extension is enabled - try { - await pool.query('CREATE EXTENSION IF NOT EXISTS vector'); - - // Create vector tables if they don't exist - await this.createVectorTables(); - - logger.info('pgvector extension initialized successfully'); - } catch (error) { - logger.error('Failed to initialize pgvector', error); - throw new Error('pgvector initialization failed'); - } + // Note: pgvector is deprecated in favor of Supabase + // This method is kept for backward compatibility but will not work in Firebase + logger.warn('pgvector provider is deprecated. Use Supabase instead for cloud deployment.'); + this.client = null; } - private async createVectorTables() { - const createTableQuery = ` - CREATE TABLE IF NOT EXISTS document_chunks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - document_id VARCHAR(255) NOT NULL, - chunk_index INTEGER NOT NULL, - content TEXT NOT NULL, - embedding vector(3072), - metadata JSONB DEFAULT '{}', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - CREATE INDEX IF NOT EXISTS document_chunks_document_id_idx ON document_chunks(document_id); - CREATE INDEX IF NOT EXISTS document_chunks_embedding_idx ON document_chunks USING ivfflat (embedding vector_cosine_ops); - `; - - await this.client.query(createTableQuery); - } private async initializeChroma() { // const { ChromaClient } = await import('chromadb'); @@ -87,6 +69,40 @@ class VectorDatabaseService { logger.info('Chroma vector database initialized'); } + private async initializeSupabase() { + try { + const { getSupabaseServiceClient } = await import('../config/supabase'); + this.client = getSupabaseServiceClient(); + + // Create the document_chunks table if it doesn't exist + await this.createSupabaseVectorTables(); + + logger.info('Supabase vector database initialized successfully'); + } catch (error) { + logger.error('Failed to initialize Supabase vector database', error); + // Don't throw error, just log it and continue without vector DB + this.client = null; + } + } + + private async createSupabaseVectorTables() { + try { + // Enable pgvector extension + await this.client.rpc('enable_pgvector'); + + // Create document_chunks table with vector support + const { error } = await this.client.rpc('create_document_chunks_table'); + + if (error && !error.message.includes('already exists')) { + throw error; + } + + logger.info('Supabase vector tables created successfully'); + } catch (error) { + logger.warn('Could not create vector tables automatically. Please run the setup SQL manually:', error); + } + } + /** * Generate embeddings for text using OpenAI or Anthropic with caching */ @@ -225,6 +241,12 @@ class VectorDatabaseService { * Store document chunks with embeddings */ async storeDocumentChunks(chunks: DocumentChunk[]): Promise { + const initialized = await this.ensureInitialized(); + if (!initialized) { + logger.warn('Vector database not available, skipping chunk storage'); + return; + } + try { switch (this.provider) { case 'pinecone': @@ -236,6 +258,9 @@ class VectorDatabaseService { case 'chroma': await this.storeInChroma(chunks); break; + case 'supabase': + await this.storeInSupabase(chunks); + break; } logger.info(`Stored ${chunks.length} document chunks in vector database`); } catch (error) { @@ -257,6 +282,12 @@ class VectorDatabaseService { enableQueryExpansion?: boolean; } = {} ): Promise { + const initialized = await this.ensureInitialized(); + if (!initialized) { + logger.warn('Vector database not available, returning empty search results'); + return []; + } + try { let queries = [query]; @@ -281,6 +312,9 @@ class VectorDatabaseService { case 'chroma': results = await this.searchChroma(embedding, options); break; + case 'supabase': + results = await this.searchSupabase(embedding, options); + break; default: throw new Error(`Unsupported provider: ${this.provider}`); } @@ -401,55 +435,14 @@ class VectorDatabaseService { } // Private implementation methods for different providers - private async storeInPinecone(chunks: DocumentChunk[]): Promise { - const index = this.client.index(config.vector.pineconeIndex!); - - const vectors = chunks.map(chunk => ({ - id: chunk.id, - values: chunk.embedding, - metadata: { - ...chunk.metadata, - documentId: chunk.documentId, - content: chunk.content - } - })); - - await index.upsert(vectors); + private async storeInPinecone(_chunks: DocumentChunk[]): Promise { + logger.warn('Pinecone provider not fully implemented'); + throw new Error('Pinecone provider not available'); } - private async storeInPgVector(chunks: DocumentChunk[]): Promise { - try { - // Delete existing chunks for this document - if (chunks.length > 0 && chunks[0]) { - await this.client.query( - 'DELETE FROM document_chunks WHERE document_id = $1', - [chunks[0].documentId] - ); - } - - // Insert new chunks with embeddings using proper pgvector format - for (const chunk of chunks) { - // Ensure embedding is properly formatted for pgvector - const embeddingArray = Array.isArray(chunk.embedding) ? chunk.embedding : []; - - await this.client.query( - `INSERT INTO document_chunks (document_id, chunk_index, content, embedding, metadata) - VALUES ($1, $2, $3, $4::vector, $5)`, - [ - chunk.documentId, - chunk.metadata?.['chunkIndex'] || 0, - chunk.content, - embeddingArray, // Pass as array, pgvector will handle the conversion - JSON.stringify(chunk.metadata || {}) - ] - ); - } - - logger.info(`Stored ${chunks.length} chunks in pgvector for document ${chunks[0]?.documentId}`); - } catch (error) { - logger.error('Failed to store chunks in pgvector', error); - throw error; - } + private async storeInPgVector(_chunks: DocumentChunk[]): Promise { + logger.warn('pgvector provider is deprecated. Use Supabase instead for cloud deployment.'); + throw new Error('pgvector provider not available in Firebase environment. Use Supabase instead.'); } private async storeInChroma(chunks: DocumentChunk[]): Promise { @@ -472,73 +465,19 @@ class VectorDatabaseService { } private async searchPinecone( - embedding: number[], - options: any + _embedding: number[], + _options: any ): Promise { - const index = this.client.index(config.vector.pineconeIndex!); - - const queryResponse = await index.query({ - vector: embedding, - topK: options.limit || 10, - filter: options.filters, - includeMetadata: true - }); - - return queryResponse.matches?.map((match: any) => ({ - id: match.id, - score: match.score, - metadata: match.metadata, - content: match.metadata.content - })) || []; + logger.warn('Pinecone provider not fully implemented'); + throw new Error('Pinecone provider not available'); } private async searchPgVector( - embedding: number[], - options: any + _embedding: number[], + _options: any ): Promise { - try { - const { documentId, limit = 5, similarity = 0.7 } = options; - - // Ensure embedding is properly formatted - const embeddingArray = Array.isArray(embedding) ? embedding : []; - - // Build query with optional document filter - let query = ` - SELECT - id, - document_id, - content, - metadata, - 1 - (embedding <=> $1::vector) as similarity - FROM document_chunks - WHERE 1 - (embedding <=> $1::vector) > $2 - `; - - const params: any[] = [embeddingArray, similarity]; - - if (documentId) { - query += ' AND document_id = $3'; - params.push(documentId); - } - - query += ' ORDER BY embedding <=> $1::vector LIMIT $' + (params.length + 1); - params.push(limit); - - const result = await this.client.query(query, params); - - return result.rows.map((row: any) => ({ - id: row.id, - documentId: row.document_id, - content: row.content, - metadata: row.metadata || {}, - similarity: row.similarity, - chunkContent: row.content, // Alias for compatibility - similarityScore: row.similarity // Add this for consistency - })); - } catch (error) { - logger.error('pgvector search failed', error); - throw error; - } + logger.warn('pgvector provider is deprecated. Use Supabase instead for cloud deployment.'); + throw new Error('pgvector provider not available in Firebase environment. Use Supabase instead.'); } private async searchChroma( @@ -563,6 +502,80 @@ class VectorDatabaseService { })); } + private async storeInSupabase(chunks: DocumentChunk[]): Promise { + try { + // Transform chunks to include embeddings + const supabaseRows = await Promise.all( + chunks.map(async (chunk) => ({ + id: chunk.id, + document_id: chunk.documentId, + chunk_index: chunk.chunkIndex, + content: chunk.content, + embedding: chunk.embedding, + metadata: chunk.metadata || {} + })) + ); + + const { error } = await this.client + .from('document_chunks') + .upsert(supabaseRows); + + if (error) { + throw error; + } + + logger.info(`Successfully stored ${chunks.length} chunks in Supabase`); + } catch (error) { + logger.error('Failed to store chunks in Supabase:', error); + throw error; + } + } + + private async searchSupabase( + embedding: number[], + options: { + documentId?: string; + limit?: number; + similarity?: number; + filters?: Record; + } + ): Promise { + try { + let query = this.client + .from('document_chunks') + .select('id, content, metadata, document_id') + .rpc('match_documents', { + query_embedding: embedding, + match_threshold: options.similarity || 0.7, + match_count: options.limit || 10 + }); + + // Add document filter if specified + if (options.documentId) { + query = query.eq('document_id', options.documentId); + } + + const { data, error } = await query; + + if (error) { + throw error; + } + + return data.map((row: any) => ({ + id: row.id, + score: row.similarity, + metadata: { + ...row.metadata, + documentId: row.document_id + }, + content: row.content + })); + } catch (error) { + logger.error('Failed to search in Supabase:', error); + return []; + } + } + private async getDocumentChunks(documentId: string): Promise { return await VectorDatabaseModel.getDocumentChunks(documentId); } diff --git a/backend/supabase_setup.sql b/backend/supabase_setup.sql new file mode 100644 index 0000000..e473c8c --- /dev/null +++ b/backend/supabase_setup.sql @@ -0,0 +1,89 @@ +-- Enable the pgvector extension +CREATE EXTENSION IF NOT EXISTS vector; + +-- Create document_chunks table with vector support +CREATE TABLE IF NOT EXISTS document_chunks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + document_id VARCHAR(255) NOT NULL, + chunk_index INTEGER NOT NULL, + content TEXT NOT NULL, + embedding vector(1536), -- OpenAI embeddings are 1536 dimensions + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes for better performance +CREATE INDEX IF NOT EXISTS document_chunks_document_id_idx ON document_chunks(document_id); +CREATE INDEX IF NOT EXISTS document_chunks_embedding_idx ON document_chunks USING ivfflat (embedding vector_cosine_ops); + +-- Create function to enable pgvector (for RPC calls) +CREATE OR REPLACE FUNCTION enable_pgvector() +RETURNS VOID AS $$ +BEGIN + CREATE EXTENSION IF NOT EXISTS vector; +END; +$$ LANGUAGE plpgsql; + +-- Create function to create document_chunks table (for RPC calls) +CREATE OR REPLACE FUNCTION create_document_chunks_table() +RETURNS VOID AS $$ +BEGIN + CREATE TABLE IF NOT EXISTS document_chunks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + document_id VARCHAR(255) NOT NULL, + chunk_index INTEGER NOT NULL, + content TEXT NOT NULL, + embedding vector(1536), + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS document_chunks_document_id_idx ON document_chunks(document_id); + CREATE INDEX IF NOT EXISTS document_chunks_embedding_idx ON document_chunks USING ivfflat (embedding vector_cosine_ops); +END; +$$ LANGUAGE plpgsql; + +-- Create function to match documents based on vector similarity +CREATE OR REPLACE FUNCTION match_documents( + query_embedding vector(1536), + match_threshold float DEFAULT 0.7, + match_count int DEFAULT 10 +) +RETURNS TABLE( + id UUID, + content TEXT, + metadata JSONB, + document_id VARCHAR(255), + similarity FLOAT +) AS $$ +BEGIN + RETURN QUERY + SELECT + document_chunks.id, + document_chunks.content, + document_chunks.metadata, + document_chunks.document_id, + 1 - (document_chunks.embedding <=> query_embedding) AS similarity + FROM document_chunks + WHERE 1 - (document_chunks.embedding <=> query_embedding) > match_threshold + ORDER BY document_chunks.embedding <=> query_embedding + LIMIT match_count; +END; +$$ LANGUAGE plpgsql; + +-- Enable Row Level Security (RLS) if needed +-- ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY; + +-- Create policies for RLS (adjust as needed for your auth requirements) +-- CREATE POLICY "Users can view all document chunks" ON document_chunks FOR SELECT USING (true); +-- CREATE POLICY "Users can insert document chunks" ON document_chunks FOR INSERT WITH CHECK (true); +-- CREATE POLICY "Users can update document chunks" ON document_chunks FOR UPDATE USING (true); +-- CREATE POLICY "Users can delete document chunks" ON document_chunks FOR DELETE USING (true); + +-- Grant necessary permissions +GRANT ALL ON document_chunks TO authenticated; +GRANT ALL ON document_chunks TO anon; +GRANT EXECUTE ON FUNCTION match_documents TO authenticated; +GRANT EXECUTE ON FUNCTION match_documents TO anon; \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production index ed52c6d..37947bf 100644 --- a/frontend/.env.production +++ b/frontend/.env.production @@ -1,4 +1,4 @@ -VITE_API_BASE_URL=https://api-y56ccs6wva-uc.a.run.app/api +VITE_API_BASE_URL=https://api-y56ccs6wva-uc.a.run.app VITE_FIREBASE_API_KEY=AIzaSyBoV04YHkbCSUIU6sXki57um4xNsvLV_jY VITE_FIREBASE_AUTH_DOMAIN=cim-summarizer.firebaseapp.com VITE_FIREBASE_PROJECT_ID=cim-summarizer diff --git a/frontend/.gcloudignore b/frontend/.gcloudignore new file mode 100644 index 0000000..5a616cb --- /dev/null +++ b/frontend/.gcloudignore @@ -0,0 +1,17 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +node_modules +#!include:.gitignore diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 43f840b..3a0716b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import DocumentViewer from './components/DocumentViewer'; import Analytics from './components/Analytics'; import LogoutButton from './components/LogoutButton'; import { documentService } from './services/documentService'; + import { Home, Upload, @@ -22,9 +23,9 @@ import { cn } from './utils/cn'; // Dashboard component const Dashboard: React.FC = () => { - const { user } = useAuth(); + const { user, token } = useAuth(); const [documents, setDocuments] = useState([]); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [viewingDocument, setViewingDocument] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [activeTab, setActiveTab] = useState<'overview' | 'documents' | 'upload' | 'analytics'>('overview'); @@ -51,13 +52,24 @@ const Dashboard: React.FC = () => { const fetchDocuments = useCallback(async () => { try { setLoading(true); - const response = await fetch('/api/documents', { + console.log('Fetching documents with token:', token ? 'Token available' : 'No token'); + console.log('User state:', user); + console.log('Token preview:', token ? `${token.substring(0, 20)}...` : 'No token'); + + if (!token) { + console.error('No authentication token available'); + return; + } + + const response = await fetch('https://us-central1-cim-summarizer.cloudfunctions.net/api/api/documents', { headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); + console.log('API response status:', response.status); + if (response.ok) { const result = await response.json(); // The API returns an array directly, not wrapped in success/data @@ -78,13 +90,17 @@ const Dashboard: React.FC = () => { })); setDocuments(transformedDocs); } + } else { + console.error('API request failed:', response.status, response.statusText); + const errorText = await response.text(); + console.error('Error response body:', errorText); } } catch (error) { console.error('Failed to fetch documents:', error); } finally { setLoading(false); } - }, [user?.name, user?.email]); + }, [user?.name, user?.email, token]); // Poll for status updates on documents that are being processed const pollDocumentStatus = useCallback(async (documentId: string) => { @@ -95,9 +111,14 @@ const Dashboard: React.FC = () => { } try { - const response = await fetch(`/api/documents/${documentId}/progress`, { + if (!token) { + console.error('No authentication token available'); + return false; + } + + const response = await fetch(`https://us-central1-cim-summarizer.cloudfunctions.net/api/api/documents/${documentId}/progress`, { headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); @@ -143,7 +164,7 @@ const Dashboard: React.FC = () => { } return true; // Continue polling - }, []); + }, [token]); // Set up polling for documents that are being processed or uploaded (might be processing) useEffect(() => { diff --git a/frontend/src/components/DocumentUpload.tsx b/frontend/src/components/DocumentUpload.tsx index b68e4d3..6b844de 100644 --- a/frontend/src/components/DocumentUpload.tsx +++ b/frontend/src/components/DocumentUpload.tsx @@ -1,8 +1,9 @@ -import React, { useCallback, useState, useRef, useEffect } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import { useDropzone } from 'react-dropzone'; import { Upload, FileText, X, CheckCircle, AlertCircle } from 'lucide-react'; import { cn } from '../utils/cn'; import { documentService } from '../services/documentService'; +import { useAuth } from '../contexts/AuthContext'; interface UploadedFile { id: string; @@ -24,6 +25,7 @@ const DocumentUpload: React.FC = ({ onUploadComplete, onUploadError, }) => { + const { token } = useAuth(); const [uploadedFiles, setUploadedFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const abortControllers = useRef>(new Map()); @@ -160,11 +162,18 @@ const DocumentUpload: React.FC = ({ return; } + // Validate UUID format + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(documentId)) { + console.warn('Attempted to monitor progress for document with invalid UUID format:', documentId); + return; + } + const checkProgress = async () => { try { - const response = await fetch(`/api/documents/${documentId}/progress`, { + const response = await fetch(`https://us-central1-cim-summarizer.cloudfunctions.net/api/api/documents/${documentId}/progress`, { headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); @@ -198,9 +207,18 @@ const DocumentUpload: React.FC = ({ if (newStatus === 'completed' || newStatus === 'error') { return; } + } else if (response.status === 404) { + // Document not found, stop monitoring + console.warn(`Document ${documentId} not found, stopping progress monitoring`); + return; + } else if (response.status === 401) { + // Unauthorized, stop monitoring + console.warn('Unauthorized access to document progress, stopping monitoring'); + return; } } catch (error) { console.error('Failed to fetch processing progress:', error); + // Don't stop monitoring on network errors, just log and continue } // Continue monitoring @@ -209,7 +227,7 @@ const DocumentUpload: React.FC = ({ // Start monitoring setTimeout(checkProgress, 1000); - }, []); + }, [token]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, diff --git a/frontend/src/components/ProcessingProgress.tsx b/frontend/src/components/ProcessingProgress.tsx index 1bbb2df..25fd0d6 100644 --- a/frontend/src/components/ProcessingProgress.tsx +++ b/frontend/src/components/ProcessingProgress.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { CheckCircle, AlertCircle, Clock, FileText, TrendingUp, Save } from 'lucide-react'; +import { useAuth } from '../contexts/AuthContext'; interface ProcessingProgressProps { documentId: string; @@ -26,6 +27,7 @@ const ProcessingProgress: React.FC = ({ onComplete, onError, }) => { + const { token } = useAuth(); const [progress, setProgress] = useState(null); const [isPolling, setIsPolling] = useState(true); @@ -62,33 +64,45 @@ const ProcessingProgress: React.FC = ({ const pollProgress = async () => { try { - const response = await fetch(`/api/documents/${documentId}/progress`, { + const response = await fetch(`https://us-central1-cim-summarizer.cloudfunctions.net/api/api/documents/${documentId}/progress`, { headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (response.ok) { - const result = await response.json(); - if (result.success) { - setProgress(result.data); - - // Handle completion - if (result.data.status === 'completed') { - setIsPolling(false); + const data = await response.json(); + setProgress({ + documentId, + jobId: data.jobId || '', + status: data.status, + step: data.step || 'validation', + progress: data.progress || 0, + message: data.message || '', + startTime: data.startTime || new Date().toISOString(), + estimatedTimeRemaining: data.estimatedTimeRemaining, + currentChunk: data.currentChunk, + totalChunks: data.totalChunks, + error: data.error, + }); + + if (data.status === 'completed' || data.status === 'failed') { + setIsPolling(false); + if (data.status === 'completed') { onComplete?.(); - } - - // Handle error - if (result.data.status === 'error') { - setIsPolling(false); - onError?.(result.data.error || 'Processing failed'); + } else { + onError?.(data.error || 'Processing failed'); } } } } catch (error) { - console.error('Failed to fetch progress:', error); + console.error('Failed to check progress:', error); + setProgress(prev => prev ? { + ...prev, + message: 'Failed to check progress', + error: 'Network error' + } : null); } }; @@ -103,7 +117,7 @@ const ProcessingProgress: React.FC = ({ pollProgress(); return () => clearInterval(interval); - }, [documentId, isPolling, onComplete, onError]); + }, [documentId, isPolling, onComplete, onError, token]); if (!progress) { return ( diff --git a/frontend/src/components/QueueStatus.tsx b/frontend/src/components/QueueStatus.tsx index 0d526ee..d62b389 100644 --- a/frontend/src/components/QueueStatus.tsx +++ b/frontend/src/components/QueueStatus.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Clock, CheckCircle, AlertCircle, PlayCircle } from 'lucide-react'; +import { useAuth } from '../contexts/AuthContext'; interface QueueStatusProps { refreshTrigger?: number; @@ -27,25 +28,29 @@ interface ProcessingJob { } const QueueStatus: React.FC = ({ refreshTrigger }) => { + const { token } = useAuth(); const [stats, setStats] = useState(null); const [activeJobs, setActiveJobs] = useState([]); const [loading, setLoading] = useState(true); const fetchQueueStatus = async () => { try { - const response = await fetch('/api/documents/queue/status', { + if (!token) { + console.error('No authentication token available'); + return; + } + + const response = await fetch('https://us-central1-cim-summarizer.cloudfunctions.net/api/api/documents/queue/status', { headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}`, + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (response.ok) { - const result = await response.json(); - if (result.success) { - setStats(result.data.stats); - setActiveJobs(result.data.activeJobs || []); - } + const data = await response.json(); + setStats(data.stats); + setActiveJobs(data.activeJobs || []); } } catch (error) { console.error('Failed to fetch queue status:', error); diff --git a/frontend/src/services/authService.ts b/frontend/src/services/authService.ts index ad33938..9304498 100644 --- a/frontend/src/services/authService.ts +++ b/frontend/src/services/authService.ts @@ -13,6 +13,8 @@ import { LoginCredentials, AuthResult, User } from '../types/auth'; class AuthService { private currentUser: FirebaseUser | null = null; private authStateListeners: Array<(user: FirebaseUser | null) => void> = []; + private tokenRefreshInterval: NodeJS.Timeout | null = null; + private isRefreshing = false; constructor() { // Listen for auth state changes @@ -21,13 +23,36 @@ class AuthService { this.updateAxiosHeaders(user); // Notify all listeners this.authStateListeners.forEach(listener => listener(user)); + + // Clear existing interval and set up new one + if (this.tokenRefreshInterval) { + clearInterval(this.tokenRefreshInterval); + } + + if (user) { + // Set up periodic token refresh (every 45 minutes to be safe) + this.tokenRefreshInterval = setInterval(async () => { + if (this.currentUser && !this.isRefreshing) { + try { + this.isRefreshing = true; + await this.updateAxiosHeaders(this.currentUser); + } catch (error) { + console.error('Periodic token refresh failed:', error); + // Don't logout on refresh failure, let the next request handle it + } finally { + this.isRefreshing = false; + } + } + }, 45 * 60 * 1000); // 45 minutes + } }); } private async updateAxiosHeaders(user: FirebaseUser | null) { if (user) { try { - const token = await getIdToken(user); + // Force token refresh to ensure we have a fresh token + const token = await getIdToken(user, true); axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; } catch (error) { console.error('Failed to get ID token:', error); @@ -40,12 +65,10 @@ class AuthService { onAuthStateChanged(callback: (user: FirebaseUser | null) => void) { this.authStateListeners.push(callback); - // Immediately call with current state - callback(this.currentUser); - - // Return unsubscribe function return () => { - this.authStateListeners = this.authStateListeners.filter(listener => listener !== callback); + this.authStateListeners = this.authStateListeners.filter( + listener => listener !== callback + ); }; } @@ -103,6 +126,10 @@ class AuthService { async logout(): Promise { try { + if (this.tokenRefreshInterval) { + clearInterval(this.tokenRefreshInterval); + this.tokenRefreshInterval = null; + } await signOut(auth); } catch (error) { console.error('Logout failed:', error); @@ -146,7 +173,21 @@ class AuthService { } try { - return await getIdToken(this.currentUser); + // Only force refresh if we're not already refreshing + if (!this.isRefreshing) { + this.isRefreshing = true; + try { + const token = await getIdToken(this.currentUser, true); + this.isRefreshing = false; + return token; + } catch (error) { + this.isRefreshing = false; + throw error; + } + } else { + // If already refreshing, just get the current token + return await getIdToken(this.currentUser, false); + } } catch (error) { console.error('Failed to get ID token:', error); return null; @@ -157,10 +198,12 @@ class AuthService { return !!this.currentUser; } - getFirebaseUser(): FirebaseUser | null { - return this.currentUser; + // Cleanup method + destroy() { + if (this.tokenRefreshInterval) { + clearInterval(this.tokenRefreshInterval); + } } } -export const authService = new AuthService(); -export default authService; \ No newline at end of file +export const authService = new AuthService(); \ No newline at end of file diff --git a/frontend/src/services/documentService.ts b/frontend/src/services/documentService.ts index a45c74f..0aaeddb 100644 --- a/frontend/src/services/documentService.ts +++ b/frontend/src/services/documentService.ts @@ -11,22 +11,40 @@ const apiClient = axios.create({ }); // Add auth token to requests -apiClient.interceptors.request.use((config) => { - const token = authService.getToken(); +apiClient.interceptors.request.use(async (config) => { + const token = await authService.getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); -// Handle auth errors +// Handle auth errors with retry logic apiClient.interceptors.response.use( (response) => response, - (error) => { - if (error.response?.status === 401) { + async (error) => { + const originalRequest = error.config; + + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + // Attempt to refresh the token + const newToken = await authService.getToken(); + if (newToken) { + // Retry the original request with the new token + originalRequest.headers.Authorization = `Bearer ${newToken}`; + return apiClient(originalRequest); + } + } catch (refreshError) { + console.error('Token refresh failed:', refreshError); + } + + // If token refresh fails, logout the user authService.logout(); window.location.href = '/login'; } + return Promise.reject(error); } ); @@ -145,7 +163,7 @@ class DocumentService { // Always use optimized agentic RAG processing - no strategy selection needed formData.append('processingStrategy', 'optimized_agentic_rag'); - const response = await apiClient.post('/documents', formData, { + const response = await apiClient.post('/api/documents', formData, { headers: { 'Content-Type': 'multipart/form-data', }, @@ -165,7 +183,7 @@ class DocumentService { * Get all documents for the current user */ async getDocuments(): Promise { - const response = await apiClient.get('/documents'); + const response = await apiClient.get('/api/documents'); return response.data; } @@ -173,7 +191,7 @@ class DocumentService { * Get a specific document by ID */ async getDocument(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}`); + const response = await apiClient.get(`/api/documents/${documentId}`); return response.data; } @@ -181,7 +199,7 @@ class DocumentService { * Get document processing status */ async getDocumentStatus(documentId: string): Promise<{ status: string; progress: number; message?: string }> { - const response = await apiClient.get(`/documents/${documentId}/progress`); + const response = await apiClient.get(`/api/documents/${documentId}/progress`); return response.data; } @@ -189,7 +207,7 @@ class DocumentService { * Download a processed document */ async downloadDocument(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}/download`, { + const response = await apiClient.get(`/api/documents/${documentId}/download`, { responseType: 'blob', }); return response.data; @@ -199,14 +217,14 @@ class DocumentService { * Delete a document */ async deleteDocument(documentId: string): Promise { - await apiClient.delete(`/documents/${documentId}`); + await apiClient.delete(`/api/documents/${documentId}`); } /** * Retry processing for a failed document */ async retryProcessing(documentId: string): Promise { - const response = await apiClient.post(`/documents/${documentId}/retry`); + const response = await apiClient.post(`/api/documents/${documentId}/retry`); return response.data; } @@ -214,14 +232,14 @@ class DocumentService { * Save CIM review data */ async saveCIMReview(documentId: string, reviewData: CIMReviewData): Promise { - await apiClient.post(`/documents/${documentId}/review`, reviewData); + await apiClient.post(`/api/documents/${documentId}/review`, reviewData); } /** * Get CIM review data for a document */ async getCIMReview(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}/review`); + const response = await apiClient.get(`/api/documents/${documentId}/review`); return response.data; } @@ -229,7 +247,7 @@ class DocumentService { * Export CIM review as PDF */ async exportCIMReview(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}/export`, { + const response = await apiClient.get(`/api/documents/${documentId}/export`, { responseType: 'blob', }); return response.data; @@ -239,7 +257,7 @@ class DocumentService { * Get document analytics and insights */ async getDocumentAnalytics(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}/analytics`); + const response = await apiClient.get(`/api/documents/${documentId}/analytics`); return response.data; } @@ -247,7 +265,7 @@ class DocumentService { * Get global analytics data */ async getAnalytics(days: number = 30): Promise { - const response = await apiClient.get('/documents/analytics', { + const response = await apiClient.get('/api/documents/analytics', { params: { days } }); return response.data; @@ -257,7 +275,7 @@ class DocumentService { * Get processing statistics */ async getProcessingStats(): Promise { - const response = await apiClient.get('/documents/processing-stats'); + const response = await apiClient.get('/api/documents/processing-stats'); return response.data; } @@ -265,7 +283,7 @@ class DocumentService { * Get agentic RAG sessions for a document */ async getAgenticRAGSessions(documentId: string): Promise { - const response = await apiClient.get(`/documents/${documentId}/agentic-rag-sessions`); + const response = await apiClient.get(`/api/documents/${documentId}/agentic-rag-sessions`); return response.data; } @@ -273,7 +291,7 @@ class DocumentService { * Get detailed agentic RAG session information */ async getAgenticRAGSessionDetails(sessionId: string): Promise { - const response = await apiClient.get(`/documents/agentic-rag-sessions/${sessionId}`); + const response = await apiClient.get(`/api/documents/agentic-rag-sessions/${sessionId}`); return response.data; } @@ -297,7 +315,7 @@ class DocumentService { * Search documents */ async searchDocuments(query: string): Promise { - const response = await apiClient.get('/documents/search', { + const response = await apiClient.get('/api/documents/search', { params: { q: query }, }); return response.data; @@ -307,7 +325,7 @@ class DocumentService { * Get processing queue status */ async getQueueStatus(): Promise<{ pending: number; processing: number; completed: number; failed: number }> { - const response = await apiClient.get('/documents/queue/status'); + const response = await apiClient.get('/api/documents/queue/status'); return response.data; }