From bba30fd2d8d6bc6843f77e3ee451c7949af12648 Mon Sep 17 00:00:00 2001 From: Amin Khorramii Date: Mon, 8 Sep 2025 22:43:43 +0200 Subject: [PATCH 1/3] first iterations - workspaces changed by projects. Scaffolded the sharing projects --- backend/api/.env.example | 15 +- backend/api/package-lock.json | 4392 +++++++++++------ backend/api/package.json | 7 +- backend/api/src/app.module.ts | 4 + .../auth/guards/optional-jwt-auth.guard.ts | 20 + .../cloud-storage/cloud-storage.controller.ts | 185 + .../src/cloud-storage/cloud-storage.module.ts | 30 + .../cloud-storage/cloud-storage.service.ts | 651 +++ .../dto/cloud-storage-response.dto.ts | 80 + .../dto/create-cloud-project.dto.ts | 27 + .../entities/cloud-file.entity.ts | 105 + .../entities/cloud-project.entity.ts | 82 + .../cloud-storage/r2-cloud-storage.service.ts | 184 + backend/api/src/main.ts | 4 +- .../1752185454248-CreateSharedFilesTable.ts | 66 + ...49-RenameCloudWorkspacesToCloudProjects.ts | 27 + ...85454250-UpdateCloudProjectsToWorkspace.ts | 53 + ...1752185454251-CreateSharedProjectsTable.ts | 100 + .../dto/create-project-share.dto.ts | 115 + .../dto/project-share-response.dto.ts | 110 + .../entities/shared-project.entity.ts | 157 + .../project-sharing.controller.ts | 140 + .../project-sharing/project-sharing.module.ts | 26 + .../project-sharing.service.ts | 566 +++ backend/workers/subdomain-router.ts | 505 ++ backend/workers/wrangler.toml | 24 + frontend/package-lock.json | 12 + frontend/package.json | 2 + frontend/src/components/auth/AuthModal.tsx | 2 +- .../src/components/cloud/SaveToCloudModal.tsx | 553 +++ .../components/data-grid/DataPreviewGrid.tsx | 77 +- .../components/data-grid/EmptyDataState.tsx | 12 +- .../data-grid/hooks/useFileUpload.ts | 16 +- .../data-sources/DataSourceManager.tsx | 50 +- .../data-sources/SourceTypeSelector.tsx | 51 - frontend/src/components/data-sources/index.ts | 5 +- frontend/src/components/layout/Sidebar.tsx | 78 +- .../project-sharing/ShareProjectModal.tsx | 276 ++ .../components/PermissionsStep.tsx | 245 + .../project-sharing/components/ReviewStep.tsx | 163 + .../components/ShareMethodStep.tsx | 82 + .../components/SuccessStep.tsx | 97 + .../project-sharing/components/index.ts | 4 + .../components/projects/CloudFilesList.tsx | 314 ++ .../{workspace => projects}/FileTreeView.tsx | 18 +- .../components/projects/ProjectSelector.tsx | 433 ++ .../projects/ProjectSelector/DraftSaver.tsx | 70 + .../ProjectSelector/ProjectCreator.tsx | 130 + .../ProjectSelector/ProjectHeader.tsx | 113 + .../projects/ProjectSelector/ProjectItem.tsx | 179 + .../projects/ProjectSelector/index.ts | 4 + .../src/components/sharing/ShareModal.tsx | 365 ++ .../workspace/WorkspaceSelector.tsx | 547 -- frontend/src/hooks/useHomePageLogic.ts | 2 + frontend/src/hooks/useSharedFileImport.ts | 68 + frontend/src/hooks/useWorkspaceState.ts | 91 - frontend/src/lib/api/apiClient.ts | 37 + frontend/src/lib/api/cloudStorageService.ts | 179 + frontend/src/lib/api/projectSharingService.ts | 260 + frontend/src/lib/api/shareService.ts | 143 + frontend/src/lib/duckdb/export/exportUtils.ts | 388 ++ frontend/src/pages/ProjectSharePreview.tsx | 482 ++ frontend/src/store/appStore.ts | 356 +- frontend/src/store/authStore.ts | 7 +- frontend/src/store/cloudStore.ts | 548 ++ frontend/src/store/projectSharingStore.ts | 238 + frontend/src/store/shareStore.ts | 282 ++ frontend/src/utils/projects.utils.test.ts | 43 + frontend/src/utils/projects.utils.ts | 17 + 69 files changed, 12382 insertions(+), 2332 deletions(-) create mode 100644 backend/api/src/auth/guards/optional-jwt-auth.guard.ts create mode 100644 backend/api/src/cloud-storage/cloud-storage.controller.ts create mode 100644 backend/api/src/cloud-storage/cloud-storage.module.ts create mode 100644 backend/api/src/cloud-storage/cloud-storage.service.ts create mode 100644 backend/api/src/cloud-storage/dto/cloud-storage-response.dto.ts create mode 100644 backend/api/src/cloud-storage/dto/create-cloud-project.dto.ts create mode 100644 backend/api/src/cloud-storage/entities/cloud-file.entity.ts create mode 100644 backend/api/src/cloud-storage/entities/cloud-project.entity.ts create mode 100644 backend/api/src/cloud-storage/r2-cloud-storage.service.ts create mode 100644 backend/api/src/migrations/1752185454248-CreateSharedFilesTable.ts create mode 100644 backend/api/src/migrations/1752185454249-RenameCloudWorkspacesToCloudProjects.ts create mode 100644 backend/api/src/migrations/1752185454250-UpdateCloudProjectsToWorkspace.ts create mode 100644 backend/api/src/migrations/1752185454251-CreateSharedProjectsTable.ts create mode 100644 backend/api/src/project-sharing/dto/create-project-share.dto.ts create mode 100644 backend/api/src/project-sharing/dto/project-share-response.dto.ts create mode 100644 backend/api/src/project-sharing/entities/shared-project.entity.ts create mode 100644 backend/api/src/project-sharing/project-sharing.controller.ts create mode 100644 backend/api/src/project-sharing/project-sharing.module.ts create mode 100644 backend/api/src/project-sharing/project-sharing.service.ts create mode 100644 backend/workers/subdomain-router.ts create mode 100644 backend/workers/wrangler.toml create mode 100644 frontend/src/components/cloud/SaveToCloudModal.tsx delete mode 100644 frontend/src/components/data-sources/SourceTypeSelector.tsx create mode 100644 frontend/src/components/project-sharing/ShareProjectModal.tsx create mode 100644 frontend/src/components/project-sharing/components/PermissionsStep.tsx create mode 100644 frontend/src/components/project-sharing/components/ReviewStep.tsx create mode 100644 frontend/src/components/project-sharing/components/ShareMethodStep.tsx create mode 100644 frontend/src/components/project-sharing/components/SuccessStep.tsx create mode 100644 frontend/src/components/project-sharing/components/index.ts create mode 100644 frontend/src/components/projects/CloudFilesList.tsx rename frontend/src/components/{workspace => projects}/FileTreeView.tsx (94%) create mode 100644 frontend/src/components/projects/ProjectSelector.tsx create mode 100644 frontend/src/components/projects/ProjectSelector/DraftSaver.tsx create mode 100644 frontend/src/components/projects/ProjectSelector/ProjectCreator.tsx create mode 100644 frontend/src/components/projects/ProjectSelector/ProjectHeader.tsx create mode 100644 frontend/src/components/projects/ProjectSelector/ProjectItem.tsx create mode 100644 frontend/src/components/projects/ProjectSelector/index.ts create mode 100644 frontend/src/components/sharing/ShareModal.tsx delete mode 100644 frontend/src/components/workspace/WorkspaceSelector.tsx create mode 100644 frontend/src/hooks/useSharedFileImport.ts delete mode 100644 frontend/src/hooks/useWorkspaceState.ts create mode 100644 frontend/src/lib/api/cloudStorageService.ts create mode 100644 frontend/src/lib/api/projectSharingService.ts create mode 100644 frontend/src/lib/api/shareService.ts create mode 100644 frontend/src/lib/duckdb/export/exportUtils.ts create mode 100644 frontend/src/pages/ProjectSharePreview.tsx create mode 100644 frontend/src/store/cloudStore.ts create mode 100644 frontend/src/store/projectSharingStore.ts create mode 100644 frontend/src/store/shareStore.ts create mode 100644 frontend/src/utils/projects.utils.test.ts create mode 100644 frontend/src/utils/projects.utils.ts diff --git a/backend/api/.env.example b/backend/api/.env.example index 30d3e12..c5f9a2a 100644 --- a/backend/api/.env.example +++ b/backend/api/.env.example @@ -36,4 +36,17 @@ BCRYPT_SALT_ROUNDS==12 ALLOWED_ORIGINS='pages.dev,datakit.page' -SLACK_WEBHOOK_URL= \ No newline at end of file +SLACK_WEBHOOK_URL= + +# R2 file sharing +R2_ACCOUNT_ID=your_account_id +R2_ACCESS_KEY_ID=your_access_key +R2_SECRET_ACCESS_KEY=your_secret_key +R2_BUCKET_NAME=datakit-shares + +R2_CLOUD_ACCOUNT_ID= +R2_CLOUD_ACCESS_KEY_ID= +R2_CLOUD_SECRET_ACCESS_KEY= +R2_CLOUD_BUCKET_NAME=datakit-cloud-storage + +SHARE_BASE_URL=https://share.datakit.page/files \ No newline at end of file diff --git a/backend/api/package-lock.json b/backend/api/package-lock.json index 7f82d9f..8fec4a3 100644 --- a/backend/api/package-lock.json +++ b/backend/api/package-lock.json @@ -9,12 +9,14 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@aws-sdk/client-s3": "^3.879.0", + "@aws-sdk/s3-request-presigner": "^3.879.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^4.0.2", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^11.0.0", "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-express": "^10.4.20", "@nestjs/schedule": "^6.0.0", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", @@ -28,6 +30,8 @@ "cookie-parser": "^1.4.7", "duckdb": "^1.3.2", "helmet": "^8.1.0", + "multer": "^2.0.2", + "nanoid": "^5.1.5", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", @@ -43,6 +47,7 @@ "@types/bcryptjs": "^2.4.6", "@types/express": "^5.0.0", "@types/jest": "^29.5.2", + "@types/multer": "^2.0.0", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", @@ -244,1983 +249,3533 @@ "tslib": "^2.1.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", - "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/core": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", - "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.7", - "@babel/types": "^7.27.7", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-s3": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.879.0.tgz", + "integrity": "sha512-1bD2Do/OdCIzl72ncHKYamDhPijUErLYpuLvciyYD4Ywt4cVLHjWtVIqb22XOOHYYHE3NqHMd4uRhvXMlsBRoQ==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/credential-provider-node": "3.879.0", + "@aws-sdk/middleware-bucket-endpoint": "3.873.0", + "@aws-sdk/middleware-expect-continue": "3.873.0", + "@aws-sdk/middleware-flexible-checksums": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-location-constraint": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-sdk-s3": "3.879.0", + "@aws-sdk/middleware-ssec": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/signature-v4-multi-region": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@aws-sdk/xml-builder": "3.873.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/eventstream-serde-browser": "^4.0.5", + "@smithy/eventstream-serde-config-resolver": "^4.1.3", + "@smithy/eventstream-serde-node": "^4.0.5", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-blob-browser": "^4.0.5", + "@smithy/hash-node": "^4.0.5", + "@smithy/hash-stream-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/md5-js": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.7", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/client-s3/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" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.879.0.tgz", + "integrity": "sha512-+Pc3OYFpRYpKLKRreovPM63FPPud1/SF9vemwIJfz6KwsBCJdvg7vYD1xLSIp5DVZLeetgf4reCyAA5ImBfZuw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/core": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.879.0.tgz", + "integrity": "sha512-AhNmLCrx980LsK+SfPXGh7YqTyZxsK0Qmy18mWmkfY0TSq7WLaSDB5zdQbgbnQCACCHy8DUYXbi4KsjlIhv3PA==", + "dependencies": { + "@aws-sdk/types": "3.862.0", + "@aws-sdk/xml-builder": "3.873.0", + "@smithy/core": "^3.9.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.879.0.tgz", + "integrity": "sha512-JgG7A8SSbr5IiCYL8kk39Y9chdSB5GPwBorDW8V8mr19G9L+qd6ohED4fAocoNFaDnYJ5wGAHhCfSJjzcsPBVQ==", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", - "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.879.0.tgz", + "integrity": "sha512-2hM5ByLpyK+qORUexjtYyDZsgxVCCUiJQZRMGkNXFEGz6zTpbjfTIWoh3zRgWHEBiqyPIyfEy50eIF69WshcuA==", "dependencies": { - "@babel/types": "^7.27.7" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-stream": "^4.2.4", + "tslib": "^2.6.2" }, - "bin": { - "parser": "bin/babel-parser.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.879.0.tgz", + "integrity": "sha512-07M8zfb73KmMBqVO5/V3Ea9kqDspMX0fO0kaI1bsjWI6ngnMye8jCE0/sIhmkVAI0aU709VA0g+Bzlopnw9EoQ==", + "dependencies": { + "@aws-sdk/core": "3.879.0", + "@aws-sdk/credential-provider-env": "3.879.0", + "@aws-sdk/credential-provider-http": "3.879.0", + "@aws-sdk/credential-provider-process": "3.879.0", + "@aws-sdk/credential-provider-sso": "3.879.0", + "@aws-sdk/credential-provider-web-identity": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.879.0.tgz", + "integrity": "sha512-FYaAqJbnSTrVL2iZkNDj2hj5087yMv2RN2GA8DJhe7iOJjzhzRojrtlfpWeJg6IhK0sBKDH+YXbdeexCzUJvtA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/credential-provider-env": "3.879.0", + "@aws-sdk/credential-provider-http": "3.879.0", + "@aws-sdk/credential-provider-ini": "3.879.0", + "@aws-sdk/credential-provider-process": "3.879.0", + "@aws-sdk/credential-provider-sso": "3.879.0", + "@aws-sdk/credential-provider-web-identity": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.879.0.tgz", + "integrity": "sha512-7r360x1VyEt35Sm1JFOzww2WpnfJNBbvvnzoyLt7WRfK0S/AfsuWhu5ltJ80QvJ0R3AiSNbG+q/btG2IHhDYPQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.879.0.tgz", + "integrity": "sha512-gd27B0NsgtKlaPNARj4IX7F7US5NuU691rGm0EUSkDsM7TctvJULighKoHzPxDQlrDbVI11PW4WtKS/Zg5zPlQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@aws-sdk/client-sso": "3.879.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/token-providers": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.879.0.tgz", + "integrity": "sha512-Jy4uPFfGzHk1Mxy+/Wr43vuw9yXsE2yiF4e4598vc3aJfO0YtA2nSfbKD3PNKRORwXbeKqWPfph9SCKQpWoxEg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.873.0.tgz", + "integrity": "sha512-b4bvr0QdADeTUs+lPc9Z48kXzbKHXQKgTvxx/jXDgSW9tv4KmYPO1gIj6Z9dcrBkRWQuUtSW3Tu2S5n6pe+zeg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-arn-parser": "3.873.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.873.0.tgz", + "integrity": "sha512-GIqoc8WgRcf/opBOZXFLmplJQKwOMjiOMmDz9gQkaJ8FiVJoAp8EGVmK2TOWZMQUYsavvHYsHaor5R2xwPoGVg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.879.0.tgz", + "integrity": "sha512-U1rcWToy2rlQPQLsx5h73uTC1XYo/JpnlJGCc3Iw7b1qrK8Mke4+rgMPKCfnXELD5TTazGrbT03frxH4Y1Ycvw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.873.0.tgz", + "integrity": "sha512-KZ/W1uruWtMOs7D5j3KquOxzCnV79KQW9MjJFZM/M0l6KI8J6V3718MXxFHsTjUE4fpdV6SeCNLV1lwGygsjJA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.873.0.tgz", + "integrity": "sha512-r+hIaORsW/8rq6wieDordXnA/eAu7xAPLue2InhoEX6ML7irP52BgiibHLpt9R0psiCzIHhju8qqKa4pJOrmiw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.876.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.876.0.tgz", + "integrity": "sha512-cpWJhOuMSyz9oV25Z/CMHCBTgafDCbv7fHR80nlRrPdPZ8ETNsahwRgltXP1QJJ8r3X/c1kwpOR7tc+RabVzNA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.873.0.tgz", + "integrity": "sha512-OtgY8EXOzRdEWR//WfPkA/fXl0+WwE8hq0y9iw2caNyKPtca85dzrrZWnPqyBK/cpImosrpR1iKMYr41XshsCg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.879.0.tgz", + "integrity": "sha512-ZTpLr2AbZcCsEzu18YCtB8Tp8tjAWHT0ccfwy3HiL6g9ncuSMW+7BVi1hDYmBidFwpPbnnIMtM0db3pDMR6/WA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-arn-parser": "3.873.0", + "@smithy/core": "^3.9.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.873.0.tgz", + "integrity": "sha512-AF55J94BoiuzN7g3hahy0dXTVZahVi8XxRBLgzNp6yQf0KTng+hb/V9UQZVYY1GZaDczvvvnqC54RGe9OZZ9zQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.879.0.tgz", + "integrity": "sha512-DDSV8228lQxeMAFKnigkd0fHzzn5aauZMYC3CSj6e5/qE7+9OwpkUcjHfb7HZ9KWG6L2/70aKZXHqiJ4xKhOZw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@smithy/core": "^3.9.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "node_modules/@aws-sdk/nested-clients": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.879.0.tgz", + "integrity": "sha512-7+n9NpIz9QtKYnxmw1fHi9C8o0GrX8LbBR4D50c7bH6Iq5+XdSuL5AFOWWQ5cMD0JhqYYJhK/fJsVau3nUtC4g==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.873.0.tgz", + "integrity": "sha512-q9sPoef+BBG6PJnc4x60vK/bfVwvRWsPgcoQyIra057S/QGjq5VkjvNk6H8xedf6vnKlXNBwq9BaANBXnldUJg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/types": "3.862.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.879.0.tgz", + "integrity": "sha512-WNUrY4UW1ZAkBiSq9HnhJcG/1NdrEy37DDxqE8u0OdIZHhbgU1x1r4iXgQssAZhV6D+Ib70oiQGtPSH/lXeMKg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/signature-v4-multi-region": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-format-url": "3.873.0", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.879.0.tgz", + "integrity": "sha512-MDsw0EWOHyKac75X3gD8tLWtmPuRliS/s4IhWRhsdDCU13wewHIs5IlA5B65kT6ISf49yEIalEH3FHUSVqdmIQ==", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@aws-sdk/middleware-sdk-s3": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", - "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/token-providers": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.879.0.tgz", + "integrity": "sha512-47J7sCwXdnw9plRZNAGVkNEOlSiLb/kR2slnDIHRK9NB/ECKsoqgz5OZQJ9E2f0yqOs8zSNJjn3T01KxpgW8Qw==", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.7", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/types": { + "version": "3.862.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.862.0.tgz", + "integrity": "sha512-Bei+RL0cDxxV+lW2UezLbCYYNeJm6Nzee0TpW0FfyTRBhH9C1XQh4+x+IClriXvgBnRquTMMYsmJfvx8iyLKrg==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/@babel/types": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", - "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.873.0.tgz", + "integrity": "sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.879.0.tgz", + "integrity": "sha512-aVAJwGecYoEmbEFju3127TyJDF9qJsKDUUTRMDuS8tGn+QiWQFnfInmbt+el9GU1gEJupNTXV+E3e74y51fb7A==", + "dependencies": { + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-endpoints": "^3.0.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.873.0.tgz", + "integrity": "sha512-v//b9jFnhzTKKV3HFTw2MakdM22uBAs2lBov51BWmFXuFtSTdBLrR7zgfetQPE3PVkFai0cmtJQPdc3MX+T/cQ==", + "dependencies": { + "@aws-sdk/types": "3.862.0", + "@smithy/querystring-builder": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.1.90" + "node": ">=18.0.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, - "license": "MIT", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz", + "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.873.0.tgz", + "integrity": "sha512-AcRdbK6o19yehEcywI43blIBhOCSo6UgyWcuOJX5CFF8k39xm1ILCjQlRRjchLAxWrm0lU0Q7XV90RiMMFMZtA==", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.879.0.tgz", + "integrity": "sha512-A5KGc1S+CJRzYnuxJQQmH1BtGsz46AgyHkqReKfGiNQA8ET/9y9LQ5t2ABqnSBHHIh3+MiCcQSkUZ0S3rTodrQ==", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18.0.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.873.0.tgz", + "integrity": "sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">= 4" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", "engines": { - "node": ">=10.10.0" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/types": "^7.27.7" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "@babel/helper-plugin-utils": "^7.12.13" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "optional": true, + "engines": { + "node": ">=0.1.90" } }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=12" } }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 4" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } + "license": "MIT" }, - "node_modules/@jridgewell/resolve-uri": { + "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=6.0.0" + "node": "*" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", - "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "devOptional": true, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" } }, - "node_modules/@ljharb/through": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", - "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.4" + "node": "*" } }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", - "license": "MIT", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", - "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", - "license": "BSD-3-Clause", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { - "consola": "^3.2.3", - "detect-libc": "^2.0.0", - "https-proxy-agent": "^7.0.5", - "node-fetch": "^2.6.7", - "nopt": "^8.0.0", - "semver": "^7.5.3", - "tar": "^7.4.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nestjs/cli": { - "version": "10.4.9", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", - "integrity": "sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "@angular-devkit/schematics-cli": "17.3.11", - "@nestjs/schematics": "^10.0.1", - "chalk": "4.1.2", - "chokidar": "3.6.0", - "cli-table3": "0.6.5", - "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "9.0.2", - "glob": "10.4.5", - "inquirer": "8.2.6", - "node-emoji": "1.11.0", - "ora": "5.4.1", - "tree-kill": "1.2.2", - "tsconfig-paths": "4.2.0", - "tsconfig-paths-webpack-plugin": "4.2.0", - "typescript": "5.7.2", - "webpack": "5.97.1", - "webpack-node-externals": "3.0.0" + "ansi-regex": "^6.0.1" }, - "bin": { - "nest": "bin/nest.js" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 16.14" + "node": ">=12" }, - "peerDependencies": { - "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", - "@swc/core": "^1.3.62" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" }, - "peerDependenciesMeta": { - "@swc/cli": { - "optional": true - }, - "@swc/core": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/cli/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "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", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/@nestjs/cli/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@nestjs/cli/node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=14.17" + "node": ">=8" } }, - "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.97.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", - "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { - "webpack": "bin/webpack.js" + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", + "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", + "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nestjs/cli": { + "version": "10.4.9", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", + "integrity": "sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "@angular-devkit/schematics-cli": "17.3.11", + "@nestjs/schematics": "^10.0.1", + "chalk": "4.1.2", + "chokidar": "3.6.0", + "cli-table3": "0.6.5", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.4.5", + "inquirer": "8.2.6", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.2.0", + "typescript": "5.7.2", + "webpack": "5.97.1", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 16.14" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@nestjs/cli/node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/@nestjs/common": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.19.tgz", + "integrity": "sha512-0TZJ8H+7qtaqZt6YfZJkDRp0e+v6jjo5/pevPAjUy0WYxaTy16bNNQxFPRKLMe/v1hUr2oGV9imvL2477zNt5g==", + "license": "MIT", + "dependencies": { + "file-type": "20.4.1", + "iterare": "1.2.1", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/core": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.19.tgz", + "integrity": "sha512-gahghu0y4Rn4gn/xPjTgNHFMpUM8TxfhdeMowVWTGVnYMZtGeEGbIXMFhJS0Dce3E4VKyqAglzgO9ecAZd4Ong==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/jwt": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz", + "integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.7", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/@nestjs/platform-express": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.20.tgz", + "integrity": "sha512-rh97mX3rimyf4xLMLHuTOBKe6UD8LOJ14VlJ1F/PTd6C6ZK9Ak6EHuJvdaGcSFQhd3ZMBh3I6CuujKGW9pNdIg==", + "dependencies": { + "body-parser": "1.20.3", + "cors": "2.8.5", + "express": "4.21.2", + "multer": "2.0.2", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + } + }, + "node_modules/@nestjs/schedule": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz", + "integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==", + "license": "MIT", + "dependencies": { + "cron": "4.3.0" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/schematics": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", + "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "comment-json": "4.2.5", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/testing": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.19.tgz", + "integrity": "sha512-YfzkjTmwEcoWqo8xr8YiTZMC4FjBEOg4uRTAPI2p6iGLWu+27tYau1CtAKFHY0uSAK3FzgtsAuYoxBSlfr9mWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + } + } + }, + "node_modules/@nestjs/throttler": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.4.0.tgz", + "integrity": "sha512-osL67i0PUuwU5nqSuJjtUJZMkxAnYB4VldgYUMGzvYRJDCqGRFMWbsbzm/CkUtPLRL30I8T74Xgt/OQxnYokiA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0" + } + }, + "node_modules/@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@npmcli/move-file/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/move-file/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.5.tgz", + "integrity": "sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.5.tgz", + "integrity": "sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.9.0.tgz", + "integrity": "sha512-B/GknvCfS3llXd/b++hcrwIuqnEozQDnRL4sBmOac5/z/dr0/yG1PURNPOyU4Lsiy1IyTj8scPxVqRs5dYWf6A==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.9", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core/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" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.7.tgz", + "integrity": "sha512-dDzrMXA8d8riFNiPvytxn0mNwR4B3h8lgrQ5UjAGu6T9z/kRg/Xncf4tEQHE/+t25sY8IH3CowcmWi+1U5B1Gw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.5.tgz", + "integrity": "sha512-miEUN+nz2UTNoRYRhRqVTJCx7jMeILdAurStT2XoS+mhokkmz1xAPp95DFW9Gxt4iF2VBqpeF9HbTQ3kY1viOA==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.2", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.5.tgz", + "integrity": "sha512-LCUQUVTbM6HFKzImYlSB9w4xafZmpdmZsOh9rIl7riPC3osCgGFVP+wwvYVw6pXda9PPT9TcEZxaq3XE81EdJQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.3.tgz", + "integrity": "sha512-yTTzw2jZjn/MbHu1pURbHdpjGbCuMHWncNBpJnQAPxOVnFUAbSIUSwafiphVDjNV93TdBJWmeVAds7yl5QCkcA==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/common": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.19.tgz", - "integrity": "sha512-0TZJ8H+7qtaqZt6YfZJkDRp0e+v6jjo5/pevPAjUy0WYxaTy16bNNQxFPRKLMe/v1hUr2oGV9imvL2477zNt5g==", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.5.tgz", + "integrity": "sha512-lGS10urI4CNzz6YlTe5EYG0YOpsSp3ra8MXyco4aqSkQDuyZPIw2hcaxDU82OUVtK7UY9hrSvgWtpsW5D4rb4g==", "dependencies": { - "file-type": "20.4.1", - "iterare": "1.2.1", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "@smithy/eventstream-serde-universal": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.5.tgz", + "integrity": "sha512-JFnmu4SU36YYw3DIBVao3FsJh4Uw65vVDIqlWT4LzR6gXA0F3KP0IXFKKJrhaVzCBhAuMsrUUaT5I+/4ZhF7aw==", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", - "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", - "license": "MIT", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.1.tgz", + "integrity": "sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==", "dependencies": { - "dotenv": "16.4.7", - "dotenv-expand": "12.0.1", - "lodash": "4.17.21" + "@smithy/protocol-http": "^5.1.3", + "@smithy/querystring-builder": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "rxjs": "^7.1.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/core": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.19.tgz", - "integrity": "sha512-gahghu0y4Rn4gn/xPjTgNHFMpUM8TxfhdeMowVWTGVnYMZtGeEGbIXMFhJS0Dce3E4VKyqAglzgO9ecAZd4Ong==", - "hasInstallScript": true, - "license": "MIT", + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.5.tgz", + "integrity": "sha512-F7MmCd3FH/Q2edhcKd+qulWkwfChHbc9nhguBlVjSUE6hVHhec3q6uPQ+0u69S6ppvLtR3eStfCuEKMXBXhvvA==", "dependencies": { - "@nuxtjs/opencollective": "0.3.2", - "fast-safe-stringify": "2.1.1", - "iterare": "1.2.1", - "path-to-regexp": "3.3.0", - "tslib": "2.8.1", - "uid": "2.0.2" + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.5.tgz", + "integrity": "sha512-cv1HHkKhpyRb6ahD8Vcfb2Hgz67vNIXEp2vnhzfxLFGRukLCNEA5QdsorbUEzXma1Rco0u3rx5VTqbM06GcZqQ==", + "dependencies": { + "@smithy/types": "^4.3.2", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@nestjs/websockets": "^10.0.0", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.5.tgz", + "integrity": "sha512-IJuDS3+VfWB67UC0GU0uYBG/TA30w+PlOaSo0GPm9UHS88A6rCP6uZxNjNYiyRtOcjv7TXn/60cW8ox1yuZsLg==", + "dependencies": { + "@smithy/types": "^4.3.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/websockets": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/jwt": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz", - "integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==", - "license": "MIT", + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.5.tgz", + "integrity": "sha512-IVnb78Qtf7EJpoEVo7qJ8BEXQwgC4n3igeJNNKEj/MLYtapnx8A67Zt/J3RXAj2xSO1910zk0LdFiygSemuLow==", "dependencies": { - "@types/jsonwebtoken": "9.0.7", - "jsonwebtoken": "9.0.2" + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/passport": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", - "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/platform-express": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.19.tgz", - "integrity": "sha512-IeQkBZUtPeJoO4E0QqSLwkB+60KcThw8/s4gGvAwIRJ5ViuXoxnwU59eBDy84PUuVbNe4VdKjfAF9fuQOEh11Q==", - "license": "MIT", + "node_modules/@smithy/md5-js": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.5.tgz", + "integrity": "sha512-8n2XCwdUbGr8W/XhMTaxILkVlw2QebkVTn5tm3HOcbPbOpWg89zr6dPXsH8xbeTsbTXlJvlJNTQsKAIoqQGbdA==", "dependencies": { - "body-parser": "1.20.3", - "cors": "2.8.5", - "express": "4.21.2", - "multer": "2.0.1", - "tslib": "2.8.1" + "@smithy/types": "^4.3.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.5.tgz", + "integrity": "sha512-l1jlNZoYzoCC7p0zCtBDE5OBXZ95yMKlRlftooE5jPWQn4YBPLgsp+oeHp7iMHaTGoUdFqmHOPa8c9G3gBsRpQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/schedule": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz", - "integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==", - "license": "MIT", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.19", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.19.tgz", + "integrity": "sha512-EAlEPncqo03siNZJ9Tm6adKCQ+sw5fNU8ncxWwaH0zTCwMPsgmERTi6CEKaermZdgJb+4Yvh0NFm36HeO4PGgQ==", "dependencies": { - "cron": "4.3.0" + "@smithy/core": "^3.9.0", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/schematics": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", - "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry": { + "version": "4.1.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.20.tgz", + "integrity": "sha512-T3maNEm3Masae99eFdx1Q7PIqBBEVOvRd5hralqKZNeIivnoGNx5OFtI3DiZ5gCjUkl0mNondlzSXeVxkinh7Q==", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "comment-json": "4.2.5", - "jsonc-parser": "3.3.1", - "pluralize": "8.0.0" + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/service-error-classification": "^4.0.7", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, - "peerDependencies": { - "typescript": ">=4.8.2" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" + "node_modules/@smithy/middleware-retry/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" + ], + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/@nestjs/testing": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.19.tgz", - "integrity": "sha512-YfzkjTmwEcoWqo8xr8YiTZMC4FjBEOg4uRTAPI2p6iGLWu+27tYau1CtAKFHY0uSAK3FzgtsAuYoxBSlfr9mWA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-serde": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.9.tgz", + "integrity": "sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==", "dependencies": { - "tslib": "2.8.1" + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.5.tgz", + "integrity": "sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.4.tgz", + "integrity": "sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==", + "dependencies": { + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/throttler": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.4.0.tgz", - "integrity": "sha512-osL67i0PUuwU5nqSuJjtUJZMkxAnYB4VldgYUMGzvYRJDCqGRFMWbsbzm/CkUtPLRL30I8T74Xgt/OQxnYokiA==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0" + "node_modules/@smithy/node-http-handler": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.1.tgz", + "integrity": "sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==", + "dependencies": { + "@smithy/abort-controller": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/querystring-builder": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nestjs/typeorm": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", - "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" + "node_modules/@smithy/property-provider": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.5.tgz", + "integrity": "sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/protocol-http": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.3.tgz", + "integrity": "sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": "^14.21.3 || >=16" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.5.tgz", + "integrity": "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==", + "dependencies": { + "@smithy/types": "^4.3.2", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.5.tgz", + "integrity": "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/service-error-classification": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.7.tgz", + "integrity": "sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@smithy/types": "^4.3.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.5.tgz", + "integrity": "sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.3.tgz", + "integrity": "sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "license": "ISC", + "node_modules/@smithy/smithy-client": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.5.0.tgz", + "integrity": "sha512-ZSdE3vl0MuVbEwJBxSftm0J5nL/gw76xp5WF13zW9cN18MFuFXD5/LV0QD8P+sCU5bSWGyy6CTgUupE1HhOo1A==", "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" + "@smithy/core": "^3.9.0", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-stream": "^4.2.4", + "tslib": "^2.6.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", + "node_modules/@smithy/types": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.2.tgz", + "integrity": "sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==", "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "tslib": "^2.6.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", + "node_modules/@smithy/url-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.5.tgz", + "integrity": "sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@smithy/querystring-parser": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dependencies": { - "brace-expansion": "^1.1.7" + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dependencies": { - "glob": "^7.1.3" + "tslib": "^2.6.2" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.27.tgz", + "integrity": "sha512-i/Fu6AFT5014VJNgWxKomBJP/GB5uuOsM4iHdcmplLm8B1eAqnRItw4lT2qpdO+mf+6TFmf6dGcggGLAVMZJsQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.5", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@nuxtjs/opencollective": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.27.tgz", + "integrity": "sha512-3W0qClMyxl/ELqTA39aNw1N+pN0IjpXT7lPFvZ8zTxqVFP7XCpACB9QufmN4FQtd39xbgS7/Lekn7LmDa63I5w==", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.0", - "node-fetch": "^2.6.1" + "@smithy/config-resolver": "^4.1.5", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "bin": { - "opencollective": "bin/opencollective.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.7.tgz", + "integrity": "sha512-klGBP+RpBp6V5JbrY2C/VKnHXn3d5V2YrifZbmMY8os7M6m8wdYFoO6w/fe5VkP+YVwrEktW3IWYaSQVNZJ8oQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "node": ">=18.0.0" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dependencies": { - "@noble/hashes": "^1.1.5" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, + "node_modules/@smithy/util-middleware": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.5.tgz", + "integrity": "sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=14" + "node": ">=18.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-retry": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.7.tgz", + "integrity": "sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.7", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.4.tgz", + "integrity": "sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/pkgr" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dependencies": { - "type-detect": "4.0.8" + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@smithy/util-waiter": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.7.tgz", + "integrity": "sha512-mYqtQXPmrwvUljaHyGxYUIIRI3qjBTEb/f5QFi3A6VlxhpmZd5mWXn9W+qUkf2pVE1Hv3SqxefiZOPGdxmO64A==", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@smithy/abort-controller": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@sqltools/formatter": { @@ -2603,6 +4158,15 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.19.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz", @@ -2726,6 +4290,11 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, "node_modules/@types/validator": { "version": "13.15.2", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz", @@ -3767,6 +5336,11 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -5755,6 +7329,23 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -8650,10 +10241,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz", - "integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==", - "license": "MIT", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", @@ -8674,6 +10264,23 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10720,6 +12327,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/strtok3": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.1.tgz", diff --git a/backend/api/package.json b/backend/api/package.json index ad352dd..a2cd0a7 100644 --- a/backend/api/package.json +++ b/backend/api/package.json @@ -35,12 +35,14 @@ "migration:create": "npm run typeorm -- migration:create" }, "dependencies": { + "@aws-sdk/client-s3": "^3.879.0", + "@aws-sdk/s3-request-presigner": "^3.879.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^4.0.2", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^11.0.0", "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-express": "^10.4.20", "@nestjs/schedule": "^6.0.0", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", @@ -54,6 +56,8 @@ "cookie-parser": "^1.4.7", "duckdb": "^1.3.2", "helmet": "^8.1.0", + "multer": "^2.0.2", + "nanoid": "^5.1.5", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", @@ -69,6 +73,7 @@ "@types/bcryptjs": "^2.4.6", "@types/express": "^5.0.0", "@types/jest": "^29.5.2", + "@types/multer": "^2.0.0", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/backend/api/src/app.module.ts b/backend/api/src/app.module.ts index ea43a98..46a18ba 100644 --- a/backend/api/src/app.module.ts +++ b/backend/api/src/app.module.ts @@ -14,6 +14,8 @@ import { AIModule } from './ai/ai.module'; import { WorkspacesModule } from './workspaces/workspaces.module'; import { WaitlistModule } from './waitlist/waitlist.module'; import { PostgresProxyModule } from './postgres-proxy/postgres-proxy.module'; +import { CloudStorageModule } from './cloud-storage/cloud-storage.module'; +import { ProjectSharingModule } from './project-sharing/project-sharing.module'; import { getDatabaseConfig } from './config/database.config'; import { CustomThrottlerGuard } from './common/guards/custom-throttler.guard'; @@ -69,6 +71,8 @@ import { CustomThrottlerGuard } from './common/guards/custom-throttler.guard'; AIModule, WaitlistModule, PostgresProxyModule, + CloudStorageModule, + ProjectSharingModule, ], controllers: [AppController], providers: [ diff --git a/backend/api/src/auth/guards/optional-jwt-auth.guard.ts b/backend/api/src/auth/guards/optional-jwt-auth.guard.ts new file mode 100644 index 0000000..88e73d3 --- /dev/null +++ b/backend/api/src/auth/guards/optional-jwt-auth.guard.ts @@ -0,0 +1,20 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class OptionalJwtAuthGuard extends AuthGuard('jwt') { + canActivate(context: ExecutionContext) { + // Add your custom authentication logic here + // for example, call super.canActivate(context) to run the standard JWT authentication + return super.canActivate(context); + } + + handleRequest(err: any, user: any) { + // Don't throw an error if the user is not authenticated + // Just return null/undefined which will be available as req.user + if (err || !user) { + return null; + } + return user; + } +} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/cloud-storage.controller.ts b/backend/api/src/cloud-storage/cloud-storage.controller.ts new file mode 100644 index 0000000..c470afb --- /dev/null +++ b/backend/api/src/cloud-storage/cloud-storage.controller.ts @@ -0,0 +1,185 @@ +import { + Controller, + Post, + Get, + Delete, + Param, + Body, + UseGuards, + UseInterceptors, + UploadedFile, + Request, + HttpCode, + HttpStatus, + Query, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { CloudStorageService } from './cloud-storage.service'; +import { CreateCloudProjectDto } from './dto/create-cloud-project.dto'; +import { SaveToCloudDto } from './dto/cloud-storage-response.dto'; + +@Controller('cloud-storage') +@UseGuards(JwtAuthGuard) +export class CloudStorageController { + constructor(private readonly cloudStorageService: CloudStorageService) {} + + /** + * Create a new cloud project in a workspace + */ + @Post('workspace/:workspaceId/project') + async createProject( + @Request() req, + @Param('workspaceId') workspaceId: string, + @Body() dto: CreateCloudProjectDto, + ) { + const userId = req.user.id; + return this.cloudStorageService.createProject(userId, workspaceId, dto); + } + + /** + * Get workspace's cloud projects + */ + @Get('workspace/:workspaceId/projects') + async getWorkspaceProjects( + @Request() req, + @Param('workspaceId') workspaceId: string, + ) { + const userId = req.user.id; + return this.cloudStorageService.getWorkspaceProjects(userId, workspaceId); + } + + /** + * Get all projects user has access to + */ + @Get('projects') + async getUserAccessibleProjects(@Request() req) { + const userId = req.user.id; + return this.cloudStorageService.getUserAccessibleProjects(userId); + } + + /** + * Upload file to cloud project + */ + @Post('upload') + @UseInterceptors( + FileInterceptor('file', { + limits: { + fileSize: 500 * 1024 * 1024, // 500MB max + }, + }), + ) + async uploadToCloud( + @Request() req, + @UploadedFile() file: Express.Multer.File, + @Body() body: any, + ) { + if (!file) { + throw new Error('No file uploaded'); + } + + const userId = req.user.id; + + // Parse metadata from body + const dto: SaveToCloudDto = { + projectId: body.projectId, // Changed from workspaceId + fileName: body.fileName, + metadata: body.metadata ? JSON.parse(body.metadata) : undefined, + replaceIfExists: body.replaceIfExists === 'true', + keepVersionHistory: body.keepVersionHistory === 'true', + }; + + return this.cloudStorageService.saveToCloud(userId, file, dto); + } + + /** + * Get files in a project + */ + @Get('project/:projectId/files') + async getProjectFiles(@Request() req, @Param('projectId') projectId: string) { + const userId = req.user.id; + return this.cloudStorageService.getProjectFiles(userId, projectId); + } + + /** + * Get all user's cloud files across all projects + */ + @Get('files') + async getAllUserFiles(@Request() req) { + const userId = req.user.id; + const projects = + await this.cloudStorageService.getUserAccessibleProjects(userId); + + const allFiles = []; + for (const project of projects) { + const files = await this.cloudStorageService.getProjectFiles( + userId, + project.id, + ); + allFiles.push( + ...files.map((file) => ({ + ...file, + projectName: project.name, + projectId: project.id, + })), + ); + } + + return allFiles; + } + + /** + * Get file access/download URL + */ + @Post('file/:fileId/access') + @HttpCode(HttpStatus.OK) + async getFileAccess(@Request() req, @Param('fileId') fileId: string) { + const userId = req.user.id; + return this.cloudStorageService.getFileAccess(userId, fileId); + } + + /** + * Delete file from cloud + */ + @Delete('file/:fileId') + async deleteFile(@Request() req, @Param('fileId') fileId: string) { + const userId = req.user.id; + await this.cloudStorageService.deleteFile(userId, fileId); + return { message: 'File deleted successfully' }; + } + + /** + * Get user's aggregated storage statistics + */ + @Get('stats') + async getUserStorageStats(@Request() req) { + const userId = req.user.id; + return this.cloudStorageService.getUserStorageStats(userId); + } + + /** + * Get workspace's storage statistics + */ + @Get('workspace/:workspaceId/stats') + async getWorkspaceStorageStats( + @Request() req, + @Param('workspaceId') workspaceId: string, + ) { + const userId = req.user.id; + + // Check access - method is private, so we'll call getWorkspaceProjects first + await this.cloudStorageService.getWorkspaceProjects(userId, workspaceId); + + return this.cloudStorageService.getWorkspaceStorageStats(workspaceId); + } + + /** + * Delete project (and all its files) + */ + @Delete('project/:projectId') + async deleteProject(@Request() req, @Param('projectId') projectId: string) { + const userId = req.user.id; + await this.cloudStorageService.deleteProject(userId, projectId); + return { message: 'Project deleted successfully' }; + } +} diff --git a/backend/api/src/cloud-storage/cloud-storage.module.ts b/backend/api/src/cloud-storage/cloud-storage.module.ts new file mode 100644 index 0000000..d0432ba --- /dev/null +++ b/backend/api/src/cloud-storage/cloud-storage.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule } from '@nestjs/config'; +import { CloudStorageController } from './cloud-storage.controller'; +import { CloudStorageService } from './cloud-storage.service'; +import { R2CloudStorageService } from './r2-cloud-storage.service'; +import { CloudProject } from './entities/cloud-project.entity'; +import { CloudFile } from './entities/cloud-file.entity'; +import { User } from '../users/entities/user.entity'; +import { Workspace } from '../workspaces/entities/workspace.entity'; +import { WorkspaceMember } from '../workspaces/entities/workspace-member.entity'; +import { Subscription } from '../subscriptions/entities/subscription.entity'; + +@Module({ + imports: [ + ConfigModule, + TypeOrmModule.forFeature([ + CloudProject, + CloudFile, + User, + Workspace, + WorkspaceMember, + Subscription, + ]), + ], + controllers: [CloudStorageController], + providers: [CloudStorageService, R2CloudStorageService], + exports: [CloudStorageService], +}) +export class CloudStorageModule {} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/cloud-storage.service.ts b/backend/api/src/cloud-storage/cloud-storage.service.ts new file mode 100644 index 0000000..78ddc10 --- /dev/null +++ b/backend/api/src/cloud-storage/cloud-storage.service.ts @@ -0,0 +1,651 @@ +import { + Injectable, + NotFoundException, + BadRequestException, + ForbiddenException, + Logger, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, Not, IsNull } from 'typeorm'; +import { CloudProject, ProjectType } from './entities/cloud-project.entity'; +import { CloudFile, CloudFileStatus } from './entities/cloud-file.entity'; +import { User } from '../users/entities/user.entity'; +import { Workspace } from '../workspaces/entities/workspace.entity'; +import { + WorkspaceMember, + WorkspaceMemberRole, +} from '../workspaces/entities/workspace-member.entity'; +import { + Subscription, + SubscriptionPlan, +} from '../subscriptions/entities/subscription.entity'; +import { R2CloudStorageService } from './r2-cloud-storage.service'; +import { CreateCloudProjectDto } from './dto/create-cloud-project.dto'; +import { + CloudProjectDto, + CloudFileDto, + CloudStorageStatsDto, + SaveToCloudDto, + CloudUploadResponseDto, + CloudAccessDto, +} from './dto/cloud-storage-response.dto'; + +// Storage limits in bytes +const STORAGE_LIMITS = { + [SubscriptionPlan.FREE]: 500 * 1024 * 1024, // 500 MB + [SubscriptionPlan.PRO]: 10 * 1024 * 1024 * 1024, // 10 GB + [SubscriptionPlan.TEAM]: 50 * 1024 * 1024 * 1024, // 50 GB +}; + +@Injectable() +export class CloudStorageService { + private readonly logger = new Logger(CloudStorageService.name); + + constructor( + @InjectRepository(CloudProject) + private cloudProjectRepository: Repository, + @InjectRepository(CloudFile) + private cloudFileRepository: Repository, + @InjectRepository(Workspace) + private workspaceRepository: Repository, + @InjectRepository(WorkspaceMember) + private workspaceMemberRepository: Repository, + @InjectRepository(User) + private userRepository: Repository, + @InjectRepository(Subscription) + private subscriptionRepository: Repository, + private r2CloudStorageService: R2CloudStorageService, + ) {} + + /** + * Check if user has access to workspace + */ + private async checkWorkspaceAccess( + userId: string, + workspaceId: string, + requiredRole?: WorkspaceMemberRole, + ): Promise { + const member = await this.workspaceMemberRepository.findOne({ + where: { + userId, + workspaceId, + }, + relations: ['workspace'], + }); + + if (!member) { + throw new ForbiddenException('You do not have access to this workspace'); + } + + // Check role if specified + if (requiredRole) { + const roleHierarchy = { + [WorkspaceMemberRole.OWNER]: 3, + [WorkspaceMemberRole.ADMIN]: 2, + [WorkspaceMemberRole.MEMBER]: 1, + }; + + if (roleHierarchy[member.role] < roleHierarchy[requiredRole]) { + throw new ForbiddenException( + `You need ${requiredRole} role to perform this action`, + ); + } + } + + return member; + } + + /** + * Get workspace's storage limit based on subscription + */ + async getWorkspaceStorageLimit(workspaceId: string): Promise { + const subscription = await this.subscriptionRepository.findOne({ + where: { workspaceId }, + }); + + const plan = subscription?.planType || SubscriptionPlan.FREE; + return STORAGE_LIMITS[plan]; + } + + /** + * Get workspace's current storage usage + */ + async getWorkspaceStorageUsage(workspaceId: string): Promise { + const result = await this.cloudProjectRepository + .createQueryBuilder('project') + .select('SUM(project.storageUsed)', 'total') + .where('project.workspaceId = :workspaceId', { workspaceId }) + .getRawOne(); + + return parseInt(result?.total || '0'); + } + + /** + * Check if workspace can upload file + */ + async checkStorageQuota( + workspaceId: string, + fileSize: number, + ): Promise { + const limit = await this.getWorkspaceStorageLimit(workspaceId); + const usage = await this.getWorkspaceStorageUsage(workspaceId); + + if (usage + fileSize > limit) { + const limitMB = Math.round(limit / (1024 * 1024)); + const usageMB = Math.round(usage / (1024 * 1024)); + throw new ForbiddenException( + `Storage limit exceeded. Workspace is using ${usageMB}MB of ${limitMB}MB available.`, + ); + } + } + + /** + * Create a new cloud project in workspace + */ + async createProject( + userId: string, + workspaceId: string, + dto: CreateCloudProjectDto, + ): Promise { + // Check workspace access + await this.checkWorkspaceAccess( + userId, + workspaceId, + WorkspaceMemberRole.MEMBER, + ); + + // Get workspace + const workspace = await this.workspaceRepository.findOne({ + where: { id: workspaceId }, + }); + + if (!workspace) { + throw new NotFoundException('Workspace not found'); + } + + // Create project + const project = this.cloudProjectRepository.create({ + workspaceId, + createdByUserId: userId, + name: dto.name, + description: dto.description, + type: dto.type || ProjectType.CLOUD, + settings: dto.settings || {}, + isDefault: dto.isDefault || false, + }); + + // If setting as default, unset other defaults in workspace + if (project.isDefault) { + await this.cloudProjectRepository.update( + { workspaceId, isDefault: true }, + { isDefault: false }, + ); + } + + const savedProject = await this.cloudProjectRepository.save(project); + return this.mapProjectToDto(savedProject); + } + + /** + * Get workspace's cloud projects + */ + async getWorkspaceProjects( + userId: string, + workspaceId: string, + ): Promise { + // Check workspace access + await this.checkWorkspaceAccess(userId, workspaceId); + + const projects = await this.cloudProjectRepository.find({ + where: { workspaceId, isActive: true }, + order: { isDefault: 'DESC', createdAt: 'DESC' }, + }); + + return projects.map((proj) => this.mapProjectToDto(proj)); + } + + /** + * Get all projects user has access to across all workspaces + */ + async getUserAccessibleProjects(userId: string): Promise { + // Get all workspaces user is member of + const memberships = await this.workspaceMemberRepository.find({ + where: { userId }, + relations: ['workspace'], + }); + + const projects: CloudProject[] = []; + + for (const membership of memberships) { + const workspaceProjects = await this.cloudProjectRepository.find({ + where: { workspaceId: membership.workspaceId, isActive: true }, + order: { isDefault: 'DESC', createdAt: 'DESC' }, + }); + projects.push(...workspaceProjects); + } + + return projects.map((proj) => this.mapProjectToDto(proj)); + } + + /** + * Save file to cloud project + */ + async saveToCloud( + userId: string, + file: Express.Multer.File, + dto: SaveToCloudDto, + ): Promise { + // Validate project + const project = await this.cloudProjectRepository.findOne({ + where: { id: dto.projectId }, + relations: ['workspace'], + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + // Check workspace access + await this.checkWorkspaceAccess(userId, project.workspaceId); + + // Check storage quota + await this.checkStorageQuota(project.workspaceId, file.size); + + // Check if file exists + const existingFile = await this.cloudFileRepository.findOne({ + where: { + projectId: dto.projectId, + fileName: dto.fileName || file.originalname, + }, + }); + + if (existingFile && !dto.replaceIfExists) { + throw new BadRequestException('File already exists in this project'); + } + + // Upload to R2 + const r2Key = `workspaces/${project.workspaceId}/projects/${dto.projectId}/files/${Date.now()}_${file.originalname}`; + const uploadResult = await this.r2CloudStorageService.uploadFile( + file.buffer, + r2Key, + file.mimetype, + ); + + // Use the actual key returned by R2 service (may include .gz suffix if compressed) + const actualR2Key = uploadResult.key; + + // Create or update file record + let cloudFile: CloudFile; + + if (existingFile && dto.replaceIfExists) { + // Keep version history if requested + if (dto.keepVersionHistory && existingFile.versions) { + existingFile.versions.push({ + versionId: `v_${Date.now()}`, + r2Key: existingFile.r2Key, + createdAt: new Date(), + fileSize: existingFile.fileSize, + createdBy: userId, + }); + } + + // Update existing file + existingFile.r2Key = actualR2Key; + existingFile.fileSize = file.size; + existingFile.compressedSize = uploadResult.compressedSize || file.size; + existingFile.mimeType = file.mimetype; + existingFile.metadata = dto.metadata; + existingFile.uploadedByUserId = userId; + existingFile.lastSyncedAt = new Date(); + + cloudFile = await this.cloudFileRepository.save(existingFile); + } else { + // Create new file + cloudFile = this.cloudFileRepository.create({ + projectId: dto.projectId, + uploadedByUserId: userId, + fileName: dto.fileName || file.originalname, + originalName: file.originalname, + fileSize: file.size, + compressedSize: uploadResult.compressedSize || file.size, + mimeType: file.mimetype, + r2Key: actualR2Key, + status: CloudFileStatus.SYNCED, + metadata: dto.metadata, + versions: [], + lastSyncedAt: new Date(), + }); + + cloudFile = await this.cloudFileRepository.save(cloudFile); + } + + // Update project storage usage + const projectFiles = await this.cloudFileRepository.find({ + where: { projectId: dto.projectId }, + }); + + const totalStorage = projectFiles.reduce( + (sum, f) => sum + Number(f.fileSize), + 0, + ); + + await this.cloudProjectRepository.update( + { id: dto.projectId }, + { + storageUsed: totalStorage, + fileCount: projectFiles.length, + }, + ); + + // Get updated project + const updatedProject = await this.cloudProjectRepository.findOne({ + where: { id: dto.projectId }, + }); + + // Get storage stats + const storageStats = await this.getWorkspaceStorageStats( + project.workspaceId, + ); + + return { + file: this.mapFileToDto(cloudFile), + project: this.mapProjectToDto(updatedProject!), + storageStats, + }; + } + + /** + * Get project files + */ + async getProjectFiles( + userId: string, + projectId: string, + ): Promise { + // Get project and check access + const project = await this.cloudProjectRepository.findOne({ + where: { id: projectId }, + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + await this.checkWorkspaceAccess(userId, project.workspaceId); + + const files = await this.cloudFileRepository.find({ + where: { projectId }, + order: { createdAt: 'DESC' }, + }); + + return files.map((file) => this.mapFileToDto(file)); + } + + /** + * Get file access/download URL + */ + async getFileAccess(userId: string, fileId: string): Promise { + const file = await this.cloudFileRepository.findOne({ + where: { id: fileId }, + relations: ['project'], + }); + + if (!file) { + throw new NotFoundException('File not found'); + } + + // Check workspace access through project + const project = await this.cloudProjectRepository.findOne({ + where: { id: file.projectId }, + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + await this.checkWorkspaceAccess(userId, project.workspaceId); + + // Generate presigned URL + const downloadUrl = await this.r2CloudStorageService.getPresignedUrl( + file.r2Key, + 3600, // 1 hour + ); + + // Update last accessed + await this.cloudFileRepository.update( + { id: fileId }, + { lastAccessedAt: new Date() }, + ); + + return { + fileId: file.id, + downloadUrl, + fileName: file.fileName, + mimeType: file.mimeType, + compressed: !!file.compressedSize && file.compressedSize < file.fileSize, + expiresIn: 3600, + }; + } + + /** + * Delete file from cloud + */ + async deleteFile(userId: string, fileId: string): Promise { + const file = await this.cloudFileRepository.findOne({ + where: { id: fileId }, + relations: ['project'], + }); + + if (!file) { + throw new NotFoundException('File not found'); + } + + // Check workspace access with admin role + const project = await this.cloudProjectRepository.findOne({ + where: { id: file.projectId }, + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + await this.checkWorkspaceAccess( + userId, + project.workspaceId, + WorkspaceMemberRole.ADMIN, + ); + + // Delete from R2 + await this.r2CloudStorageService.deleteFile(file.r2Key); + + // Delete database record + await this.cloudFileRepository.remove(file); + + // Update project storage + const remainingFiles = await this.cloudFileRepository.find({ + where: { projectId: file.projectId }, + }); + + const totalStorage = remainingFiles.reduce( + (sum, f) => sum + Number(f.fileSize), + 0, + ); + + await this.cloudProjectRepository.update( + { id: file.projectId }, + { + storageUsed: totalStorage, + fileCount: remainingFiles.length, + }, + ); + } + + /** + * Get workspace storage statistics + */ + /** + * Get user's aggregated storage statistics across all accessible workspaces + */ + async getUserStorageStats(userId: string): Promise { + // Get all workspaces the user has access to + const memberships = await this.workspaceMemberRepository.find({ + where: { userId, acceptedAt: Not(IsNull()) }, + relations: ['workspace'], + }); + + let totalUsage = 0; + let totalLimit = 0; + let totalFiles = 0; + let totalProjects = 0; + let primaryPlan = SubscriptionPlan.FREE; + + for (const membership of memberships) { + const workspaceId = membership.workspaceId; + + // Get workspace storage usage and limits + const usage = await this.getWorkspaceStorageUsage(workspaceId); + const limit = await this.getWorkspaceStorageLimit(workspaceId); + + totalUsage += usage; + totalLimit += limit; + + // Count projects and files in this workspace + const projectCount = await this.cloudProjectRepository.count({ + where: { workspaceId }, + }); + + const fileCount = await this.cloudFileRepository + .createQueryBuilder('file') + .innerJoin('file.project', 'project') + .where('project.workspaceId = :workspaceId', { workspaceId }) + .getCount(); + + totalProjects += projectCount; + totalFiles += fileCount; + + // Get the highest tier plan across all workspaces + const subscription = await this.subscriptionRepository.findOne({ + where: { workspaceId }, + }); + + if (subscription) { + if (subscription.planType === SubscriptionPlan.TEAM) { + primaryPlan = SubscriptionPlan.TEAM; + } else if ( + subscription.planType === SubscriptionPlan.PRO && + primaryPlan === SubscriptionPlan.FREE + ) { + primaryPlan = SubscriptionPlan.PRO; + } + } + } + + return { + totalStorageUsed: totalUsage.toString(), + storageLimit: totalLimit.toString(), + storagePercentage: totalLimit > 0 ? (totalUsage / totalLimit) * 100 : 0, + totalFiles, + totalProjects, + plan: primaryPlan, + }; + } + + async getWorkspaceStorageStats( + workspaceId: string, + ): Promise { + const subscription = await this.subscriptionRepository.findOne({ + where: { workspaceId }, + }); + + const limit = await this.getWorkspaceStorageLimit(workspaceId); + const usage = await this.getWorkspaceStorageUsage(workspaceId); + + const projects = await this.cloudProjectRepository.count({ + where: { workspaceId }, + }); + + const files = await this.cloudFileRepository + .createQueryBuilder('file') + .innerJoin('file.project', 'project') + .where('project.workspaceId = :workspaceId', { workspaceId }) + .getCount(); + + return { + totalStorageUsed: usage.toString(), + storageLimit: limit.toString(), + storagePercentage: (usage / limit) * 100, + totalFiles: files, + totalProjects: projects, + plan: subscription?.planType || SubscriptionPlan.FREE, + }; + } + + /** + * Delete project (and all its files) + */ + async deleteProject(userId: string, projectId: string): Promise { + const project = await this.cloudProjectRepository.findOne({ + where: { id: projectId }, + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + // Check workspace access with admin role + await this.checkWorkspaceAccess( + userId, + project.workspaceId, + WorkspaceMemberRole.ADMIN, + ); + + // Get all files in project + const files = await this.cloudFileRepository.find({ + where: { projectId }, + }); + + // Delete all files from R2 + for (const file of files) { + await this.r2CloudStorageService.deleteFile(file.r2Key); + } + + // Delete project (cascade will delete files) + await this.cloudProjectRepository.remove(project); + } + + // Helper methods + private mapProjectToDto(project: CloudProject): CloudProjectDto { + return { + id: project.id, + name: project.name, + description: project.description, + type: project.type, + storageUsed: project.storageUsed?.toString() || '0', + fileCount: project.fileCount, + settings: project.settings, + isDefault: project.isDefault, + isActive: project.isActive, + createdAt: project.createdAt, + updatedAt: project.updatedAt, + }; + } + + private mapFileToDto(file: CloudFile): CloudFileDto { + return { + id: file.id, + projectId: file.projectId, + fileName: file.fileName, + originalName: file.originalName, + fileSize: file.fileSize?.toString() || '0', + compressedSize: file.compressedSize?.toString(), + mimeType: file.mimeType, + status: file.status, + metadata: file.metadata, + isShared: file.isShared, + sharedFileId: file.sharedFileId, + lastAccessedAt: file.lastAccessedAt, + lastSyncedAt: file.lastSyncedAt, + createdAt: file.createdAt, + updatedAt: file.updatedAt, + }; + } +} diff --git a/backend/api/src/cloud-storage/dto/cloud-storage-response.dto.ts b/backend/api/src/cloud-storage/dto/cloud-storage-response.dto.ts new file mode 100644 index 0000000..e5d624d --- /dev/null +++ b/backend/api/src/cloud-storage/dto/cloud-storage-response.dto.ts @@ -0,0 +1,80 @@ +import { CloudFileStatus } from '../entities/cloud-file.entity'; +import { ProjectType } from '../entities/cloud-project.entity'; + +export class CloudProjectDto { + id: string; + name: string; + description?: string; + type: ProjectType; + storageUsed: string; + fileCount: number; + settings?: { + autoSync?: boolean; + versioningEnabled?: boolean; + defaultFileFormat?: string; + }; + isDefault: boolean; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +export class CloudFileDto { + id: string; + projectId: string; + fileName: string; + originalName: string; + fileSize: string; + compressedSize?: string; + mimeType: string; + status: CloudFileStatus; + metadata?: { + rowCount?: number; + columnCount?: number; + fileType?: string; + compressed?: boolean; + }; + isShared: boolean; + sharedFileId?: string; + lastAccessedAt?: Date; + lastSyncedAt?: Date; + createdAt: Date; + updatedAt: Date; +} + +export class CloudStorageStatsDto { + totalStorageUsed: string; + storageLimit: string; + storagePercentage: number; + totalFiles: number; + totalProjects: number; + plan: string; +} + +export class SaveToCloudDto { + projectId: string; + fileName?: string; + metadata?: { + rowCount?: number; + columnCount?: number; + fileType?: string; + tableName?: string; + }; + replaceIfExists?: boolean; + keepVersionHistory?: boolean; +} + +export class CloudUploadResponseDto { + file: CloudFileDto; + project: CloudProjectDto; + storageStats: CloudStorageStatsDto; +} + +export class CloudAccessDto { + fileId: string; + downloadUrl: string; + fileName: string; + mimeType: string; + compressed: boolean; + expiresIn: number; +} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/dto/create-cloud-project.dto.ts b/backend/api/src/cloud-storage/dto/create-cloud-project.dto.ts new file mode 100644 index 0000000..8b321e2 --- /dev/null +++ b/backend/api/src/cloud-storage/dto/create-cloud-project.dto.ts @@ -0,0 +1,27 @@ +import { IsString, IsOptional, IsBoolean, IsObject } from 'class-validator'; +import { ProjectType } from '../entities/cloud-project.entity'; + +export class CreateCloudProjectDto { + @IsString() + name: string; + + @IsString() + @IsOptional() + description?: string; + + @IsString() + @IsOptional() + type?: ProjectType; + + @IsObject() + @IsOptional() + settings?: { + autoSync?: boolean; + versioningEnabled?: boolean; + defaultFileFormat?: string; + }; + + @IsBoolean() + @IsOptional() + isDefault?: boolean; +} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/entities/cloud-file.entity.ts b/backend/api/src/cloud-storage/entities/cloud-file.entity.ts new file mode 100644 index 0000000..3445098 --- /dev/null +++ b/backend/api/src/cloud-storage/entities/cloud-file.entity.ts @@ -0,0 +1,105 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + Index, +} from 'typeorm'; +import { CloudProject } from './cloud-project.entity'; +import { User } from '../../users/entities/user.entity'; + +export enum CloudFileStatus { + SYNCED = 'synced', + SYNCING = 'syncing', + PENDING = 'pending', + ERROR = 'error', +} + +@Entity('cloud_files') +@Index(['projectId', 'uploadedByUserId']) +@Index(['fileName']) +export class CloudFile { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + projectId: string; + + @ManyToOne(() => CloudProject, project => project.files, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'projectId' }) + project: CloudProject; + + @Column({ nullable: true }) + uploadedByUserId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'uploadedByUserId' }) + uploadedBy: User; + + @Column() + fileName: string; + + @Column() + originalName: string; + + @Column({ type: 'bigint' }) + fileSize: number; + + @Column({ type: 'bigint', nullable: true }) + compressedSize: number; + + @Column() + mimeType: string; + + @Column() + r2Key: string; + + @Column({ + type: 'enum', + enum: CloudFileStatus, + default: CloudFileStatus.SYNCED, + }) + status: CloudFileStatus; + + @Column({ type: 'jsonb', nullable: true }) + metadata: { + rowCount?: number; + columnCount?: number; + fileType?: string; + schema?: any; + tableName?: string; + compressed?: boolean; + lastModifiedLocally?: Date; + }; + + @Column({ type: 'jsonb', nullable: true }) + versions: Array<{ + versionId: string; + r2Key: string; + createdAt: Date; + fileSize: number; + createdBy: string; + comment?: string; + }>; + + @Column({ default: false }) + isShared: boolean; + + @Column({ nullable: true }) + sharedFileId: string; + + @Column({ type: 'timestamp', nullable: true }) + lastAccessedAt: Date; + + @Column({ type: 'timestamp', nullable: true }) + lastSyncedAt: Date; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/entities/cloud-project.entity.ts b/backend/api/src/cloud-storage/entities/cloud-project.entity.ts new file mode 100644 index 0000000..1ffa6c3 --- /dev/null +++ b/backend/api/src/cloud-storage/entities/cloud-project.entity.ts @@ -0,0 +1,82 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + OneToMany, + JoinColumn, + Index, +} from 'typeorm'; +import { User } from '../../users/entities/user.entity'; +import { Workspace } from '../../workspaces/entities/workspace.entity'; +import { CloudFile } from './cloud-file.entity'; + +export enum ProjectType { + LOCAL = 'local', + CLOUD = 'cloud', +} + +@Entity('cloud_projects') +@Index(['workspaceId']) +@Index(['createdByUserId']) +export class CloudProject { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + workspaceId: string; + + @ManyToOne(() => Workspace) + @JoinColumn({ name: 'workspaceId' }) + workspace: Workspace; + + @Column({ nullable: true }) + createdByUserId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'createdByUserId' }) + createdBy: User; + + @Column() + name: string; + + @Column({ nullable: true }) + description: string; + + @Column({ + type: 'enum', + enum: ProjectType, + default: ProjectType.CLOUD, + }) + type: ProjectType; + + @Column({ type: 'bigint', default: 0 }) + storageUsed: number; + + @Column({ default: 0 }) + fileCount: number; + + @Column({ type: 'jsonb', nullable: true }) + settings: { + autoSync?: boolean; + versioningEnabled?: boolean; + defaultFileFormat?: string; + }; + + @Column({ default: false }) + isDefault: boolean; + + @Column({ default: true }) + isActive: boolean; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @OneToMany(() => CloudFile, file => file.project) + files: CloudFile[]; +} \ No newline at end of file diff --git a/backend/api/src/cloud-storage/r2-cloud-storage.service.ts b/backend/api/src/cloud-storage/r2-cloud-storage.service.ts new file mode 100644 index 0000000..dc0d081 --- /dev/null +++ b/backend/api/src/cloud-storage/r2-cloud-storage.service.ts @@ -0,0 +1,184 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + DeleteObjectCommand, + HeadObjectCommand, +} from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import * as zlib from 'zlib'; +import { promisify } from 'util'; + +const gzip = promisify(zlib.gzip); +const gunzip = promisify(zlib.gunzip); + +@Injectable() +export class R2CloudStorageService { + private readonly logger = new Logger(R2CloudStorageService.name); + private s3Client: S3Client; + private bucketName: string; + + constructor(private configService: ConfigService) { + const accountId = this.configService.get('R2_CLOUD_ACCOUNT_ID'); + const accessKeyId = this.configService.get('R2_CLOUD_ACCESS_KEY_ID'); + const secretAccessKey = this.configService.get('R2_CLOUD_SECRET_ACCESS_KEY'); + this.bucketName = this.configService.get('R2_CLOUD_BUCKET_NAME', 'datakit-cloud-storage'); + + this.s3Client = new S3Client({ + region: 'auto', + endpoint: `https://${accountId}.r2.cloudflarestorage.com`, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); + } + + /** + * Upload file to R2 with optional compression + */ + async uploadFile( + buffer: Buffer, + key: string, + mimeType: string, + ): Promise<{ key: string; compressed: boolean; compressedSize?: number }> { + let uploadBuffer = buffer; + let compressed = false; + let compressedSize: number | undefined; + + // Compress text-based files + const compressibleTypes = ['text/csv', 'application/json', 'text/plain', 'text/tab-separated-values']; + if (compressibleTypes.includes(mimeType)) { + try { + const compressedBuffer = await gzip(buffer); + // Only use compression if it reduces size by at least 10% + if (compressedBuffer.length < buffer.length * 0.9) { + uploadBuffer = compressedBuffer; + compressed = true; + compressedSize = compressedBuffer.length; + key = `${key}.gz`; + this.logger.log( + `Compression completed: ${buffer.length} → ${compressedBuffer.length} bytes (${( + ((buffer.length - compressedBuffer.length) / buffer.length) * + 100 + ).toFixed(2)}% reduction)`, + ); + } + } catch (error) { + this.logger.error('Compression failed, uploading uncompressed:', error); + } + } + + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: uploadBuffer, + ContentType: compressed ? 'application/gzip' : mimeType, + Metadata: { + originalMimeType: mimeType, + compressed: compressed.toString(), + originalSize: buffer.length.toString(), + }, + }); + + await this.s3Client.send(command); + this.logger.log(`File uploaded successfully to R2: ${key}`); + + return { + key, + compressed, + compressedSize, + }; + } + + /** + * Get presigned URL for file download + */ + async getPresignedUrl(key: string, expiresIn: number = 300): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }); + + const url = await getSignedUrl(this.s3Client, command, { expiresIn }); + return url; + } + + /** + * Delete file from R2 + */ + async deleteFile(key: string): Promise { + try { + const command = new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: key, + }); + + await this.s3Client.send(command); + this.logger.log(`File deleted from R2: ${key}`); + } catch (error) { + this.logger.error(`Failed to delete file from R2: ${key}`, error); + throw error; + } + } + + /** + * Check if file exists + */ + async fileExists(key: string): Promise { + try { + const command = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: key, + }); + + await this.s3Client.send(command); + return true; + } catch (error) { + if (error.name === 'NotFound') { + return false; + } + throw error; + } + } + + /** + * Download and decompress file if needed + */ + async downloadFile(key: string): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key, + }); + + const response = await this.s3Client.send(command); + const buffer = await this.streamToBuffer(response.Body); + + // Check if file is compressed + if (key.endsWith('.gz') || response.Metadata?.compressed === 'true') { + try { + const decompressed = await gunzip(buffer); + this.logger.log(`File decompressed: ${key}`); + return decompressed; + } catch (error) { + this.logger.error('Decompression failed:', error); + return buffer; + } + } + + return buffer; + } + + /** + * Convert stream to buffer + */ + private async streamToBuffer(stream: any): Promise { + const chunks: Buffer[] = []; + for await (const chunk of stream) { + chunks.push(Buffer.from(chunk)); + } + return Buffer.concat(chunks); + } +} \ No newline at end of file diff --git a/backend/api/src/main.ts b/backend/api/src/main.ts index ab10f9c..6ec7d57 100644 --- a/backend/api/src/main.ts +++ b/backend/api/src/main.ts @@ -10,7 +10,9 @@ import { } from './utils/cors.utils'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + bodyParser: true, + }); const isProduction = process.env.NODE_ENV === 'production'; if (isProduction) { diff --git a/backend/api/src/migrations/1752185454248-CreateSharedFilesTable.ts b/backend/api/src/migrations/1752185454248-CreateSharedFilesTable.ts new file mode 100644 index 0000000..984280e --- /dev/null +++ b/backend/api/src/migrations/1752185454248-CreateSharedFilesTable.ts @@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateSharedFilesTable1752185454248 implements MigrationInterface { + name = 'CreateSharedFilesTable1752185454248' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TYPE "public"."shared_files_accesstype_enum" AS ENUM('public', 'email_list') + `); + + await queryRunner.query(` + CREATE TABLE "shared_files" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "shareId" character varying(12) NOT NULL, + "userId" uuid, + "fileName" character varying NOT NULL, + "fileSize" bigint NOT NULL, + "compressedSize" bigint, + "mimeType" character varying, + "r2Key" character varying(500) NOT NULL, + "accessType" "public"."shared_files_accesstype_enum" NOT NULL DEFAULT 'public', + "allowedEmails" text, + "requireAuth" boolean NOT NULL DEFAULT true, + "fileMetadata" jsonb, + "accessCount" integer NOT NULL DEFAULT '0', + "accessLogs" jsonb NOT NULL DEFAULT '[]', + "createdAt" TIMESTAMP NOT NULL DEFAULT now(), + "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), + "expiresAt" TIMESTAMP, + "lastAccessedAt" TIMESTAMP, + CONSTRAINT "UQ_share_id" UNIQUE ("shareId"), + CONSTRAINT "PK_shared_files" PRIMARY KEY ("id") + ) + `); + + await queryRunner.query(` + CREATE INDEX "idx_share_id" ON "shared_files" ("shareId") + `); + + await queryRunner.query(` + CREATE INDEX "idx_user_id" ON "shared_files" ("userId") + `); + + await queryRunner.query(` + CREATE INDEX "idx_expires_at" ON "shared_files" ("expiresAt") + `); + + await queryRunner.query(` + ALTER TABLE "shared_files" + ADD CONSTRAINT "FK_shared_files_user" + FOREIGN KEY ("userId") + REFERENCES "users"("id") + ON DELETE CASCADE + ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "shared_files" DROP CONSTRAINT "FK_shared_files_user"`); + await queryRunner.query(`DROP INDEX "public"."idx_expires_at"`); + await queryRunner.query(`DROP INDEX "public"."idx_user_id"`); + await queryRunner.query(`DROP INDEX "public"."idx_share_id"`); + await queryRunner.query(`DROP TABLE "shared_files"`); + await queryRunner.query(`DROP TYPE "public"."shared_files_accesstype_enum"`); + } +} \ No newline at end of file diff --git a/backend/api/src/migrations/1752185454249-RenameCloudWorkspacesToCloudProjects.ts b/backend/api/src/migrations/1752185454249-RenameCloudWorkspacesToCloudProjects.ts new file mode 100644 index 0000000..6f37fcf --- /dev/null +++ b/backend/api/src/migrations/1752185454249-RenameCloudWorkspacesToCloudProjects.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RenameCloudWorkspacesToCloudProjects1752185454249 implements MigrationInterface { + name = 'RenameCloudWorkspacesToCloudProjects1752185454249' + + public async up(queryRunner: QueryRunner): Promise { + // Rename table + await queryRunner.query(`ALTER TABLE "cloud_workspaces" RENAME TO "cloud_projects"`); + + // Rename foreign key in cloud_files table + await queryRunner.query(`ALTER TABLE "cloud_files" RENAME COLUMN "workspaceId" TO "projectId"`); + + // Update indexes if needed + await queryRunner.query(`ALTER INDEX "IDX_cloud_workspaces_userId" RENAME TO "IDX_cloud_projects_userId"`); + + // Rename enum type if it exists + await queryRunner.query(`ALTER TYPE "cloud_workspaces_type_enum" RENAME TO "cloud_projects_type_enum"`); + } + + public async down(queryRunner: QueryRunner): Promise { + // Reverse the changes + await queryRunner.query(`ALTER TABLE "cloud_projects" RENAME TO "cloud_workspaces"`); + await queryRunner.query(`ALTER TABLE "cloud_files" RENAME COLUMN "projectId" TO "workspaceId"`); + await queryRunner.query(`ALTER INDEX "IDX_cloud_projects_userId" RENAME TO "IDX_cloud_workspaces_userId"`); + await queryRunner.query(`ALTER TYPE "cloud_projects_type_enum" RENAME TO "cloud_workspaces_type_enum"`); + } +} \ No newline at end of file diff --git a/backend/api/src/migrations/1752185454250-UpdateCloudProjectsToWorkspace.ts b/backend/api/src/migrations/1752185454250-UpdateCloudProjectsToWorkspace.ts new file mode 100644 index 0000000..67849ae --- /dev/null +++ b/backend/api/src/migrations/1752185454250-UpdateCloudProjectsToWorkspace.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateCloudProjectsToWorkspace1752185454250 implements MigrationInterface { + name = 'UpdateCloudProjectsToWorkspace1752185454250' + + public async up(queryRunner: QueryRunner): Promise { + // Add workspaceId column to cloud_projects + await queryRunner.query(`ALTER TABLE "cloud_projects" ADD "workspaceId" uuid`); + + // Migrate existing user projects to their personal workspace + // For each user with projects, find or create their personal workspace + await queryRunner.query(` + UPDATE cloud_projects cp + SET "workspaceId" = w.id + FROM workspaces w + WHERE w."ownerId" = cp."userId" + AND w."isPersonal" = true + `); + + // Make workspaceId NOT NULL after migration + await queryRunner.query(`ALTER TABLE "cloud_projects" ALTER COLUMN "workspaceId" SET NOT NULL`); + + // Add foreign key constraint + await queryRunner.query(` + ALTER TABLE "cloud_projects" + ADD CONSTRAINT "FK_cloud_projects_workspace" + FOREIGN KEY ("workspaceId") + REFERENCES "workspaces"("id") + ON DELETE CASCADE + `); + + // Create index on workspaceId + await queryRunner.query(`CREATE INDEX "IDX_cloud_projects_workspaceId" ON "cloud_projects" ("workspaceId")`); + + // Optionally remove userId column (or keep for audit) + // await queryRunner.query(`ALTER TABLE "cloud_projects" DROP COLUMN "userId"`); + + // Keep userId but rename it to createdByUserId for audit trail + await queryRunner.query(`ALTER TABLE "cloud_projects" RENAME COLUMN "userId" TO "createdByUserId"`); + + // Update cloud_files to track who uploaded (optional) + await queryRunner.query(`ALTER TABLE "cloud_files" RENAME COLUMN "userId" TO "uploadedByUserId"`); + } + + public async down(queryRunner: QueryRunner): Promise { + // Reverse the changes + await queryRunner.query(`ALTER TABLE "cloud_files" RENAME COLUMN "uploadedByUserId" TO "userId"`); + await queryRunner.query(`ALTER TABLE "cloud_projects" RENAME COLUMN "createdByUserId" TO "userId"`); + await queryRunner.query(`DROP INDEX "IDX_cloud_projects_workspaceId"`); + await queryRunner.query(`ALTER TABLE "cloud_projects" DROP CONSTRAINT "FK_cloud_projects_workspace"`); + await queryRunner.query(`ALTER TABLE "cloud_projects" DROP COLUMN "workspaceId"`); + } +} \ No newline at end of file diff --git a/backend/api/src/migrations/1752185454251-CreateSharedProjectsTable.ts b/backend/api/src/migrations/1752185454251-CreateSharedProjectsTable.ts new file mode 100644 index 0000000..4b18255 --- /dev/null +++ b/backend/api/src/migrations/1752185454251-CreateSharedProjectsTable.ts @@ -0,0 +1,100 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateSharedProjectsTable1752185454251 implements MigrationInterface { + name = 'CreateSharedProjectsTable1752185454251' + + public async up(queryRunner: QueryRunner): Promise { + // Create enum types + await queryRunner.query(` + CREATE TYPE "shared_projects_access_type_enum" AS ENUM( + 'public', 'authenticated', 'email_list' + ) + `); + + await queryRunner.query(` + CREATE TYPE "shared_projects_permission_enum" AS ENUM( + 'view', 'query', 'ai', 'export' + ) + `); + + // Create shared_projects table + await queryRunner.query(` + CREATE TABLE "shared_projects" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "shareId" character varying(12) NOT NULL, + "projectId" uuid NOT NULL, + "workspaceId" uuid NOT NULL, + "createdByUserId" uuid NOT NULL, + "customSlug" character varying(50), + "accessType" "shared_projects_access_type_enum" NOT NULL DEFAULT 'authenticated', + "allowedEmails" text, + "requireAuth" boolean NOT NULL DEFAULT true, + "permissions" text NOT NULL DEFAULT 'view', + "expiresAt" TIMESTAMP, + "settings" jsonb, + "viewCount" integer NOT NULL DEFAULT 0, + "uniqueViewers" integer NOT NULL DEFAULT 0, + "lastAccessedAt" TIMESTAMP, + "accessLogs" jsonb NOT NULL DEFAULT '[]', + "isActive" boolean NOT NULL DEFAULT true, + "isPublic" boolean NOT NULL DEFAULT false, + "createdAt" TIMESTAMP NOT NULL DEFAULT now(), + "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), + CONSTRAINT "PK_shared_projects" PRIMARY KEY ("id"), + CONSTRAINT "UQ_shared_projects_shareId" UNIQUE ("shareId"), + CONSTRAINT "UQ_shared_projects_customSlug" UNIQUE ("customSlug") + ) + `); + + // Create indexes + await queryRunner.query(`CREATE INDEX "IDX_shared_projects_shareId" ON "shared_projects" ("shareId")`); + await queryRunner.query(`CREATE INDEX "IDX_shared_projects_customSlug" ON "shared_projects" ("customSlug")`); + await queryRunner.query(`CREATE INDEX "IDX_shared_projects_workspaceId" ON "shared_projects" ("workspaceId")`); + await queryRunner.query(`CREATE INDEX "IDX_shared_projects_projectId" ON "shared_projects" ("projectId")`); + + // Add foreign keys + await queryRunner.query(` + ALTER TABLE "shared_projects" + ADD CONSTRAINT "FK_shared_projects_project" + FOREIGN KEY ("projectId") + REFERENCES "cloud_projects"("id") + ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "shared_projects" + ADD CONSTRAINT "FK_shared_projects_workspace" + FOREIGN KEY ("workspaceId") + REFERENCES "workspaces"("id") + ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "shared_projects" + ADD CONSTRAINT "FK_shared_projects_user" + FOREIGN KEY ("createdByUserId") + REFERENCES "users"("id") + ON DELETE SET NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise { + // Drop foreign keys + await queryRunner.query(`ALTER TABLE "shared_projects" DROP CONSTRAINT "FK_shared_projects_user"`); + await queryRunner.query(`ALTER TABLE "shared_projects" DROP CONSTRAINT "FK_shared_projects_workspace"`); + await queryRunner.query(`ALTER TABLE "shared_projects" DROP CONSTRAINT "FK_shared_projects_project"`); + + // Drop indexes + await queryRunner.query(`DROP INDEX "IDX_shared_projects_projectId"`); + await queryRunner.query(`DROP INDEX "IDX_shared_projects_workspaceId"`); + await queryRunner.query(`DROP INDEX "IDX_shared_projects_customSlug"`); + await queryRunner.query(`DROP INDEX "IDX_shared_projects_shareId"`); + + // Drop table + await queryRunner.query(`DROP TABLE "shared_projects"`); + + // Drop enums + await queryRunner.query(`DROP TYPE "shared_projects_permission_enum"`); + await queryRunner.query(`DROP TYPE "shared_projects_access_type_enum"`); + } +} \ No newline at end of file diff --git a/backend/api/src/project-sharing/dto/create-project-share.dto.ts b/backend/api/src/project-sharing/dto/create-project-share.dto.ts new file mode 100644 index 0000000..a59df01 --- /dev/null +++ b/backend/api/src/project-sharing/dto/create-project-share.dto.ts @@ -0,0 +1,115 @@ +import { + IsString, + IsOptional, + IsBoolean, + IsArray, + IsEnum, + IsDateString, + IsObject, + Length, + Matches, + ArrayNotEmpty, +} from 'class-validator'; +import { ShareAccessType, SharePermission } from '../entities/shared-project.entity'; + +export class CreateProjectShareDto { + @IsString() + projectId: string; + + @IsOptional() + @IsString() + @Length(3, 50) + @Matches(/^[a-z0-9-]+$/, { + message: 'Custom slug must contain only lowercase letters, numbers, and hyphens', + }) + customSlug?: string; + + @IsOptional() + @IsEnum(ShareAccessType) + accessType?: ShareAccessType; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + allowedEmails?: string[]; + + @IsOptional() + @IsBoolean() + requireAuth?: boolean; + + @IsOptional() + @IsArray() + @ArrayNotEmpty() + @IsEnum(SharePermission, { each: true }) + permissions?: SharePermission[]; + + @IsOptional() + @IsDateString() + expiresAt?: string; + + @IsOptional() + @IsObject() + settings?: { + showOwnerInfo?: boolean; + allowDownload?: boolean; + allowQueryExecution?: boolean; + allowAIUsage?: boolean; + showWatermark?: boolean; + customBranding?: { + title?: string; + description?: string; + logoUrl?: string; + primaryColor?: string; + }; + }; +} + +export class UpdateProjectShareDto { + @IsOptional() + @IsString() + @Length(3, 50) + @Matches(/^[a-z0-9-]+$/) + customSlug?: string; + + @IsOptional() + @IsEnum(ShareAccessType) + accessType?: ShareAccessType; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + allowedEmails?: string[]; + + @IsOptional() + @IsBoolean() + requireAuth?: boolean; + + @IsOptional() + @IsArray() + @IsEnum(SharePermission, { each: true }) + permissions?: SharePermission[]; + + @IsOptional() + @IsDateString() + expiresAt?: string; + + @IsOptional() + @IsObject() + settings?: { + showOwnerInfo?: boolean; + allowDownload?: boolean; + allowQueryExecution?: boolean; + allowAIUsage?: boolean; + showWatermark?: boolean; + customBranding?: { + title?: string; + description?: string; + logoUrl?: string; + primaryColor?: string; + }; + }; + + @IsOptional() + @IsBoolean() + isActive?: boolean; +} \ No newline at end of file diff --git a/backend/api/src/project-sharing/dto/project-share-response.dto.ts b/backend/api/src/project-sharing/dto/project-share-response.dto.ts new file mode 100644 index 0000000..df56e03 --- /dev/null +++ b/backend/api/src/project-sharing/dto/project-share-response.dto.ts @@ -0,0 +1,110 @@ +import { ShareAccessType, SharePermission } from '../entities/shared-project.entity'; + +export class ProjectShareResponseDto { + id: string; + shareId: string; + shareUrl: string; + customSlug?: string; + projectId: string; + projectName: string; + workspaceId: string; + workspaceName: string; + accessType: ShareAccessType; + permissions: SharePermission[]; + requireAuth: boolean; + allowedEmails?: string[]; + expiresAt?: Date; + settings?: { + showOwnerInfo?: boolean; + allowDownload?: boolean; + allowQueryExecution?: boolean; + allowAIUsage?: boolean; + showWatermark?: boolean; + customBranding?: { + title?: string; + description?: string; + logoUrl?: string; + primaryColor?: string; + }; + }; + viewCount: number; + uniqueViewers: number; + lastAccessedAt?: Date; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +export class ProjectSharePreviewDto { + shareId: string; + customSlug?: string; + projectName: string; + projectDescription?: string; + workspaceName: string; + ownerName?: string; + fileCount: number; + totalSize: string; + lastUpdated: Date; + accessType: ShareAccessType; + requireAuth: boolean; + permissions: SharePermission[]; + settings?: { + showOwnerInfo?: boolean; + customBranding?: { + title?: string; + description?: string; + logoUrl?: string; + primaryColor?: string; + }; + }; + files: ProjectFilePreviewDto[]; + isExpired: boolean; + createdAt: Date; +} + +export class ProjectFilePreviewDto { + id: string; + fileName: string; + fileSize: string; + mimeType: string; + metadata?: { + rowCount?: number; + columnCount?: number; + fileType?: string; + }; + lastModified: Date; +} + +export class ProjectShareAccessDto { + shareId: string; + accessGranted: boolean; + message?: string; + projectData?: { + name: string; + description?: string; + files: { + id: string; + name: string; + downloadUrl: string; + metadata?: any; + }[]; + permissions: SharePermission[]; + }; + expiresIn?: number; +} + +export class ProjectShareAnalyticsDto { + shareId: string; + totalViews: number; + uniqueViewers: number; + viewsToday: number; + viewsThisWeek: number; + viewsThisMonth: number; + topCountries: { country: string; views: number }[]; + recentActivity: { + timestamp: Date; + action: string; + userEmail?: string; + ipAddress?: string; + }[]; +} \ No newline at end of file diff --git a/backend/api/src/project-sharing/entities/shared-project.entity.ts b/backend/api/src/project-sharing/entities/shared-project.entity.ts new file mode 100644 index 0000000..2521f57 --- /dev/null +++ b/backend/api/src/project-sharing/entities/shared-project.entity.ts @@ -0,0 +1,157 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + Index, + Unique, +} from 'typeorm'; +import { CloudProject } from '../../cloud-storage/entities/cloud-project.entity'; +import { Workspace } from '../../workspaces/entities/workspace.entity'; +import { User } from '../../users/entities/user.entity'; + +export enum ShareAccessType { + PUBLIC = 'public', + AUTHENTICATED = 'authenticated', + EMAIL_LIST = 'email_list', +} + +export enum SharePermission { + VIEW = 'view', + QUERY = 'query', + AI = 'ai', + EXPORT = 'export', +} + +export interface AccessLog { + timestamp: Date; + userId?: string; + userEmail?: string; + ipAddress?: string; + userAgent?: string; + action: 'preview' | 'access' | 'query' | 'export'; +} + +@Entity('shared_projects') +@Index(['shareId']) +@Index(['customSlug']) +@Index(['workspaceId']) +@Unique(['customSlug']) // Ensure unique custom subdomains +export class SharedProject { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ unique: true, length: 12 }) + shareId: string; // Short unique ID for URLs + + @Column() + projectId: string; + + @ManyToOne(() => CloudProject, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'projectId' }) + project: CloudProject; + + @Column() + workspaceId: string; + + @ManyToOne(() => Workspace) + @JoinColumn({ name: 'workspaceId' }) + workspace: Workspace; + + @Column() + createdByUserId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'createdByUserId' }) + createdBy: User; + + // Custom subdomain/slug for branding + @Column({ nullable: true, length: 50 }) + customSlug: string; // For {custom-slug}.datakit.page + + // Access control + @Column({ + type: 'enum', + enum: ShareAccessType, + default: ShareAccessType.AUTHENTICATED, + }) + accessType: ShareAccessType; + + @Column({ type: 'simple-array', nullable: true }) + allowedEmails: string[]; + + @Column({ default: true }) + requireAuth: boolean; + + // Permissions + @Column({ + type: 'simple-array', + default: [SharePermission.VIEW], + }) + permissions: SharePermission[]; + + // Expiration + @Column({ type: 'timestamp', nullable: true }) + expiresAt: Date; + + // Settings + @Column({ type: 'jsonb', nullable: true }) + settings: { + showOwnerInfo?: boolean; + allowDownload?: boolean; + allowQueryExecution?: boolean; + allowAIUsage?: boolean; + showWatermark?: boolean; + customBranding?: { + title?: string; + description?: string; + logoUrl?: string; + primaryColor?: string; + }; + }; + + // Analytics + @Column({ default: 0 }) + viewCount: number; + + @Column({ default: 0 }) + uniqueViewers: number; + + @Column({ type: 'timestamp', nullable: true }) + lastAccessedAt: Date; + + @Column({ type: 'jsonb', default: () => "'[]'" }) + accessLogs: AccessLog[]; + + // Status + @Column({ default: true }) + isActive: boolean; + + @Column({ default: false }) + isPublic: boolean; // Quick flag for public shares + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + // Computed properties + get shareUrl(): string { + if (this.customSlug) { + return `https://${this.customSlug}.datakit.page`; + } + return `https://share.datakit.page/p/${this.shareId}`; + } + + get isExpired(): boolean { + return this.expiresAt ? this.expiresAt < new Date() : false; + } + + get hasPermission(): (permission: SharePermission) => boolean { + return (permission: SharePermission) => this.permissions.includes(permission); + } +} \ No newline at end of file diff --git a/backend/api/src/project-sharing/project-sharing.controller.ts b/backend/api/src/project-sharing/project-sharing.controller.ts new file mode 100644 index 0000000..6cfd26b --- /dev/null +++ b/backend/api/src/project-sharing/project-sharing.controller.ts @@ -0,0 +1,140 @@ +import { + Controller, + Post, + Get, + Put, + Delete, + Param, + Body, + UseGuards, + Request, + Headers, + HttpCode, + HttpStatus, + Query, +} from '@nestjs/common'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { OptionalJwtAuthGuard } from '../auth/guards/optional-jwt-auth.guard'; +import { ProjectSharingService } from './project-sharing.service'; +import { CreateProjectShareDto, UpdateProjectShareDto } from './dto/create-project-share.dto'; + +@Controller('project-sharing') +export class ProjectSharingController { + constructor(private readonly projectSharingService: ProjectSharingService) {} + + /** + * Create a new project share (requires authentication) + */ + @Post('share') + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.CREATED) + async createProjectShare( + @Request() req, + @Body() dto: CreateProjectShareDto, + ) { + const userId = req.user.id; + return this.projectSharingService.createProjectShare(userId, dto); + } + + /** + * Get project share preview (public endpoint) + */ + @Get('preview/:identifier') + async getProjectSharePreview(@Param('identifier') identifier: string) { + return this.projectSharingService.getProjectSharePreview(identifier); + } + + /** + * Access a shared project (optionally authenticated) + */ + @Post('access/:identifier') + @UseGuards(OptionalJwtAuthGuard) + @HttpCode(HttpStatus.OK) + async accessSharedProject( + @Param('identifier') identifier: string, + @Request() req, + @Headers('x-forwarded-for') ipAddress?: string, + @Headers('user-agent') userAgent?: string, + ) { + const userId = req.user?.id; + const userEmail = req.user?.email; + + return this.projectSharingService.accessSharedProject( + identifier, + userId, + userEmail, + ipAddress, + userAgent, + ); + } + + /** + * Update project share (requires authentication) + */ + @Put('share/:shareId') + @UseGuards(JwtAuthGuard) + async updateProjectShare( + @Request() req, + @Param('shareId') shareId: string, + @Body() dto: UpdateProjectShareDto, + ) { + const userId = req.user.id; + return this.projectSharingService.updateProjectShare(userId, shareId, dto); + } + + /** + * Delete project share (requires authentication) + */ + @Delete('share/:shareId') + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteProjectShare( + @Request() req, + @Param('shareId') shareId: string, + ) { + const userId = req.user.id; + await this.projectSharingService.deleteProjectShare(userId, shareId); + } + + /** + * Get user's project shares (requires authentication) + */ + @Get('my-shares') + @UseGuards(JwtAuthGuard) + async getUserProjectShares(@Request() req) { + const userId = req.user.id; + return this.projectSharingService.getUserProjectShares(userId); + } + + /** + * Get project share analytics (requires authentication) + */ + @Get('share/:shareId/analytics') + @UseGuards(JwtAuthGuard) + async getProjectShareAnalytics( + @Request() req, + @Param('shareId') shareId: string, + ) { + const userId = req.user.id; + return this.projectSharingService.getProjectShareAnalytics(userId, shareId); + } + + /** + * Check custom slug availability (requires authentication) + */ + @Get('check-slug/:slug') + @UseGuards(JwtAuthGuard) + async checkSlugAvailability(@Param('slug') slug: string) { + try { + // Try to create a temporary share to validate slug + // This will throw if slug is invalid or taken + await this.projectSharingService['validateCustomSlug'](slug); + return { available: true }; + } catch (error) { + return { + available: false, + message: error instanceof Error ? error.message : 'Slug not available' + }; + } + } +} \ No newline at end of file diff --git a/backend/api/src/project-sharing/project-sharing.module.ts b/backend/api/src/project-sharing/project-sharing.module.ts new file mode 100644 index 0000000..af603b8 --- /dev/null +++ b/backend/api/src/project-sharing/project-sharing.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProjectSharingController } from './project-sharing.controller'; +import { ProjectSharingService } from './project-sharing.service'; +import { SharedProject } from './entities/shared-project.entity'; +import { CloudProject } from '../cloud-storage/entities/cloud-project.entity'; +import { CloudFile } from '../cloud-storage/entities/cloud-file.entity'; +import { Workspace } from '../workspaces/entities/workspace.entity'; +import { WorkspaceMember } from '../workspaces/entities/workspace-member.entity'; +import { R2CloudStorageService } from '../cloud-storage/r2-cloud-storage.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + SharedProject, + CloudProject, + CloudFile, + Workspace, + WorkspaceMember, + ]), + ], + controllers: [ProjectSharingController], + providers: [ProjectSharingService, R2CloudStorageService], + exports: [ProjectSharingService], +}) +export class ProjectSharingModule {} \ No newline at end of file diff --git a/backend/api/src/project-sharing/project-sharing.service.ts b/backend/api/src/project-sharing/project-sharing.service.ts new file mode 100644 index 0000000..6ccd630 --- /dev/null +++ b/backend/api/src/project-sharing/project-sharing.service.ts @@ -0,0 +1,566 @@ +import { + Injectable, + NotFoundException, + BadRequestException, + ForbiddenException, + ConflictException, + Logger, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, In } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; +import { randomBytes } from 'crypto'; +import { + SharedProject, + ShareAccessType, + SharePermission, + AccessLog, +} from './entities/shared-project.entity'; +import { CloudProject } from '../cloud-storage/entities/cloud-project.entity'; +import { CloudFile } from '../cloud-storage/entities/cloud-file.entity'; +import { Workspace } from '../workspaces/entities/workspace.entity'; +import { WorkspaceMember, WorkspaceMemberRole } from '../workspaces/entities/workspace-member.entity'; +import { R2CloudStorageService } from '../cloud-storage/r2-cloud-storage.service'; +import { CreateProjectShareDto, UpdateProjectShareDto } from './dto/create-project-share.dto'; +import { + ProjectShareResponseDto, + ProjectSharePreviewDto, + ProjectFilePreviewDto, + ProjectShareAccessDto, + ProjectShareAnalyticsDto, +} from './dto/project-share-response.dto'; + +@Injectable() +export class ProjectSharingService { + private readonly logger = new Logger(ProjectSharingService.name); + private readonly reservedSlugs = [ + 'www', 'api', 'app', 'admin', 'support', 'help', 'docs', 'blog', + 'mail', 'ftp', 'cdn', 'assets', 'static', 'share', 'preview', + ]; + + constructor( + @InjectRepository(SharedProject) + private sharedProjectRepository: Repository, + @InjectRepository(CloudProject) + private cloudProjectRepository: Repository, + @InjectRepository(CloudFile) + private cloudFileRepository: Repository, + @InjectRepository(Workspace) + private workspaceRepository: Repository, + @InjectRepository(WorkspaceMember) + private workspaceMemberRepository: Repository, + private r2CloudStorageService: R2CloudStorageService, + private configService: ConfigService, + ) {} + + /** + * Check if user has permission to share project + */ + private async checkProjectSharePermission( + userId: string, + projectId: string, + ): Promise<{ project: CloudProject; workspace: Workspace }> { + const project = await this.cloudProjectRepository.findOne({ + where: { id: projectId }, + relations: ['workspace'], + }); + + if (!project) { + throw new NotFoundException('Project not found'); + } + + // Check workspace membership + const member = await this.workspaceMemberRepository.findOne({ + where: { + userId, + workspaceId: project.workspaceId, + }, + }); + + if (!member) { + throw new ForbiddenException('You do not have access to this project'); + } + + // Only admins and owners can share projects + if (![WorkspaceMemberRole.OWNER, WorkspaceMemberRole.ADMIN].includes(member.role)) { + throw new ForbiddenException('You do not have permission to share this project'); + } + + return { project, workspace: project.workspace }; + } + + /** + * Generate unique share ID + */ + private generateShareId(): string { + return randomBytes(9).toString('base64url'); + } + + /** + * Validate custom slug + */ + private async validateCustomSlug(slug: string, excludeId?: string): Promise { + if (this.reservedSlugs.includes(slug.toLowerCase())) { + throw new BadRequestException('This slug is reserved'); + } + + const existing = await this.sharedProjectRepository.findOne({ + where: { customSlug: slug }, + }); + + if (existing && existing.id !== excludeId) { + throw new ConflictException('This custom URL is already taken'); + } + } + + /** + * Create a new project share + */ + async createProjectShare( + userId: string, + dto: CreateProjectShareDto, + ): Promise { + // Validate project access + const { project, workspace } = await this.checkProjectSharePermission( + userId, + dto.projectId, + ); + + // Check if project is already shared + const existingShare = await this.sharedProjectRepository.findOne({ + where: { projectId: dto.projectId, isActive: true }, + }); + + if (existingShare) { + throw new ConflictException('This project is already shared'); + } + + // Validate custom slug if provided + if (dto.customSlug) { + await this.validateCustomSlug(dto.customSlug); + } + + // Generate unique share ID + const shareId = this.generateShareId(); + + // Set default permissions based on access type + let permissions = dto.permissions || [SharePermission.VIEW]; + if (dto.accessType === ShareAccessType.PUBLIC && dto.requireAuth !== false) { + // For public shares, limit permissions unless explicitly overridden + permissions = permissions.filter(p => + [SharePermission.VIEW, SharePermission.EXPORT].includes(p) + ); + } + + // Create shared project + const sharedProject = this.sharedProjectRepository.create({ + shareId, + projectId: dto.projectId, + workspaceId: project.workspaceId, + createdByUserId: userId, + customSlug: dto.customSlug, + accessType: dto.accessType || ShareAccessType.AUTHENTICATED, + allowedEmails: dto.allowedEmails, + requireAuth: dto.requireAuth !== false, + permissions, + expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null, + settings: { + showOwnerInfo: dto.settings?.showOwnerInfo !== false, + allowDownload: dto.settings?.allowDownload !== false, + allowQueryExecution: dto.settings?.allowQueryExecution || false, + allowAIUsage: dto.settings?.allowAIUsage || false, + showWatermark: dto.settings?.showWatermark !== false, + customBranding: dto.settings?.customBranding, + }, + isPublic: dto.accessType === ShareAccessType.PUBLIC, + accessLogs: [], + }); + + const savedShare = await this.sharedProjectRepository.save(sharedProject); + + this.logger.log(`Created project share ${shareId} for project ${dto.projectId} by user ${userId}`); + + return this.mapToResponseDto(savedShare, project, workspace); + } + + /** + * Get project share preview (public endpoint) + */ + async getProjectSharePreview( + identifier: string, // shareId or customSlug + ): Promise { + let sharedProject: SharedProject | null; + + // Try to find by custom slug first, then by shareId + if (identifier.length > 12) { + sharedProject = await this.sharedProjectRepository.findOne({ + where: { customSlug: identifier }, + relations: ['project', 'workspace'], + }); + } else { + sharedProject = await this.sharedProjectRepository.findOne({ + where: { shareId: identifier }, + relations: ['project', 'workspace'], + }); + } + + if (!sharedProject || !sharedProject.isActive) { + throw new NotFoundException('Shared project not found'); + } + + // Check if expired + if (sharedProject.isExpired) { + throw new BadRequestException('This share has expired'); + } + + // Get project files + const files = await this.cloudFileRepository.find({ + where: { projectId: sharedProject.projectId }, + order: { createdAt: 'DESC' }, + }); + + // Calculate total size + const totalSize = files.reduce((sum, file) => sum + Number(file.fileSize || 0), 0); + + // Map files to preview format + const filesPreviews: ProjectFilePreviewDto[] = files.map(file => ({ + id: file.id, + fileName: file.fileName, + fileSize: file.fileSize?.toString() || '0', + mimeType: file.mimeType, + metadata: { + rowCount: file.metadata?.rowCount, + columnCount: file.metadata?.columnCount, + fileType: file.metadata?.fileType, + }, + lastModified: file.updatedAt, + })); + + return { + shareId: sharedProject.shareId, + customSlug: sharedProject.customSlug, + projectName: sharedProject.project.name, + projectDescription: sharedProject.project.description, + workspaceName: sharedProject.workspace.name, + ownerName: sharedProject.settings?.showOwnerInfo ? + sharedProject.workspace.name : undefined, + fileCount: files.length, + totalSize: this.formatFileSize(totalSize), + lastUpdated: sharedProject.project.updatedAt, + accessType: sharedProject.accessType, + requireAuth: sharedProject.requireAuth, + permissions: sharedProject.permissions, + settings: { + showOwnerInfo: sharedProject.settings?.showOwnerInfo, + customBranding: sharedProject.settings?.customBranding, + }, + files: filesPreviews, + isExpired: sharedProject.isExpired, + createdAt: sharedProject.createdAt, + }; + } + + /** + * Access a shared project (requires authentication/permission) + */ + async accessSharedProject( + identifier: string, + userId?: string, + userEmail?: string, + ipAddress?: string, + userAgent?: string, + ): Promise { + // Get shared project + let sharedProject: SharedProject | null; + + if (identifier.length > 12) { + sharedProject = await this.sharedProjectRepository.findOne({ + where: { customSlug: identifier }, + relations: ['project', 'workspace'], + }); + } else { + sharedProject = await this.sharedProjectRepository.findOne({ + where: { shareId: identifier }, + relations: ['project', 'workspace'], + }); + } + + if (!sharedProject || !sharedProject.isActive) { + throw new NotFoundException('Shared project not found'); + } + + // Check if expired + if (sharedProject.isExpired) { + throw new BadRequestException('This share has expired'); + } + + // Check access permissions + if (sharedProject.requireAuth && !userId) { + return { + shareId: sharedProject.shareId, + accessGranted: false, + message: 'Authentication required to access this project', + }; + } + + // Check email restrictions + if (sharedProject.accessType === ShareAccessType.EMAIL_LIST) { + if (!userEmail || !sharedProject.allowedEmails?.includes(userEmail)) { + return { + shareId: sharedProject.shareId, + accessGranted: false, + message: 'Your email is not authorized to access this project', + }; + } + } + + // Get project files with download URLs + const files = await this.cloudFileRepository.find({ + where: { projectId: sharedProject.projectId }, + }); + + const projectFiles = await Promise.all( + files.map(async (file) => { + let downloadUrl: string | undefined; + + // Only generate download URL if user has export permission + if (sharedProject.permissions.includes(SharePermission.EXPORT)) { + try { + downloadUrl = await this.r2CloudStorageService.getPresignedUrl( + file.r2Key, + 3600, // 1 hour + ); + } catch (error) { + this.logger.warn(`Failed to generate download URL for file ${file.id}:`, error); + } + } + + return { + id: file.id, + name: file.fileName, + downloadUrl: downloadUrl || '', + metadata: file.metadata, + }; + }) + ); + + // Log access + const accessLog: AccessLog = { + timestamp: new Date(), + userId, + userEmail, + ipAddress, + userAgent, + action: 'access', + }; + + // Update analytics + await this.updateAnalytics(sharedProject, accessLog); + + return { + shareId: sharedProject.shareId, + accessGranted: true, + projectData: { + name: sharedProject.project.name, + description: sharedProject.project.description, + files: projectFiles, + permissions: sharedProject.permissions, + }, + expiresIn: sharedProject.expiresAt ? + Math.floor((sharedProject.expiresAt.getTime() - Date.now()) / 1000) : + undefined, + }; + } + + /** + * Update analytics for a shared project + */ + private async updateAnalytics( + sharedProject: SharedProject, + accessLog: AccessLog, + ): Promise { + // Add to access logs (keep only last 100 entries) + const logs = [...(sharedProject.accessLogs || []), accessLog].slice(-100); + + // Update view counts + const viewCount = sharedProject.viewCount + 1; + + // Count unique viewers (rough estimate based on userId/email/IP) + const uniqueIdentifiers = new Set(); + logs.forEach(log => { + const identifier = log.userId || log.userEmail || log.ipAddress; + if (identifier) uniqueIdentifiers.add(identifier); + }); + + await this.sharedProjectRepository.update(sharedProject.id, { + viewCount, + uniqueViewers: uniqueIdentifiers.size, + lastAccessedAt: new Date(), + accessLogs: logs, + }); + } + + /** + * Update project share + */ + async updateProjectShare( + userId: string, + shareId: string, + dto: UpdateProjectShareDto, + ): Promise { + const sharedProject = await this.sharedProjectRepository.findOne({ + where: { shareId }, + relations: ['project', 'workspace'], + }); + + if (!sharedProject) { + throw new NotFoundException('Shared project not found'); + } + + // Check permission + await this.checkProjectSharePermission(userId, sharedProject.projectId); + + // Validate custom slug if being updated + if (dto.customSlug && dto.customSlug !== sharedProject.customSlug) { + await this.validateCustomSlug(dto.customSlug, sharedProject.id); + } + + // Update fields + Object.assign(sharedProject, { + ...dto, + expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : undefined, + isPublic: dto.accessType === ShareAccessType.PUBLIC, + settings: dto.settings ? { ...sharedProject.settings, ...dto.settings } : sharedProject.settings, + }); + + const updatedShare = await this.sharedProjectRepository.save(sharedProject); + + return this.mapToResponseDto( + updatedShare, + sharedProject.project, + sharedProject.workspace, + ); + } + + /** + * Delete project share + */ + async deleteProjectShare(userId: string, shareId: string): Promise { + const sharedProject = await this.sharedProjectRepository.findOne({ + where: { shareId }, + }); + + if (!sharedProject) { + throw new NotFoundException('Shared project not found'); + } + + // Check permission + await this.checkProjectSharePermission(userId, sharedProject.projectId); + + await this.sharedProjectRepository.remove(sharedProject); + + this.logger.log(`Deleted project share ${shareId} by user ${userId}`); + } + + /** + * Get project shares for a user + */ + async getUserProjectShares(userId: string): Promise { + // Get all workspaces user is member of + const memberships = await this.workspaceMemberRepository.find({ + where: { userId }, + }); + + const workspaceIds = memberships.map(m => m.workspaceId); + + const shares = await this.sharedProjectRepository.find({ + where: { workspaceId: In(workspaceIds) }, + relations: ['project', 'workspace'], + order: { createdAt: 'DESC' }, + }); + + return shares.map(share => this.mapToResponseDto( + share, + share.project, + share.workspace, + )); + } + + /** + * Get analytics for a shared project + */ + async getProjectShareAnalytics( + userId: string, + shareId: string, + ): Promise { + const sharedProject = await this.sharedProjectRepository.findOne({ + where: { shareId }, + }); + + if (!sharedProject) { + throw new NotFoundException('Shared project not found'); + } + + // Check permission + await this.checkProjectSharePermission(userId, sharedProject.projectId); + + const logs = sharedProject.accessLogs || []; + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const thisWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000); + const thisMonth = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); + + return { + shareId: sharedProject.shareId, + totalViews: sharedProject.viewCount, + uniqueViewers: sharedProject.uniqueViewers, + viewsToday: logs.filter(log => new Date(log.timestamp) >= today).length, + viewsThisWeek: logs.filter(log => new Date(log.timestamp) >= thisWeek).length, + viewsThisMonth: logs.filter(log => new Date(log.timestamp) >= thisMonth).length, + topCountries: [], // Would need IP geolocation service + recentActivity: logs.slice(-10).map(log => ({ + timestamp: new Date(log.timestamp), + action: log.action, + userEmail: log.userEmail, + ipAddress: log.ipAddress, + })), + }; + } + + // Helper methods + private mapToResponseDto( + share: SharedProject, + project: CloudProject, + workspace: Workspace, + ): ProjectShareResponseDto { + return { + id: share.id, + shareId: share.shareId, + shareUrl: share.shareUrl, + customSlug: share.customSlug, + projectId: share.projectId, + projectName: project.name, + workspaceId: share.workspaceId, + workspaceName: workspace.name, + accessType: share.accessType, + permissions: share.permissions, + requireAuth: share.requireAuth, + allowedEmails: share.allowedEmails, + expiresAt: share.expiresAt, + settings: share.settings, + viewCount: share.viewCount, + uniqueViewers: share.uniqueViewers, + lastAccessedAt: share.lastAccessedAt, + isActive: share.isActive, + createdAt: share.createdAt, + updatedAt: share.updatedAt, + }; + } + + private formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; + } +} \ No newline at end of file diff --git a/backend/workers/subdomain-router.ts b/backend/workers/subdomain-router.ts new file mode 100644 index 0000000..bb7e0cb --- /dev/null +++ b/backend/workers/subdomain-router.ts @@ -0,0 +1,505 @@ +/** + * Cloudflare Worker for handling subdomain routing + * Handles *.datakit.page requests and routes them appropriately + */ + +interface Env { + BACKEND_URL: string; + FRONTEND_URL: string; + ALLOWED_ORIGINS: string; +} + +// Reserved subdomains that should not be used for project sharing +const RESERVED_SUBDOMAINS = new Set([ + 'www', 'api', 'app', 'admin', 'support', 'help', 'docs', 'blog', + 'mail', 'ftp', 'cdn', 'assets', 'static', 'share', 'preview', + 'dashboard', 'auth', 'login', 'signup', 'account', 'settings', + 'status', 'health', 'monitoring', 'analytics', 'reports', +]); + +export default { + async fetch(request: Request, env: Env): Promise { + const url = new URL(request.url); + const hostname = url.hostname; + + // Extract subdomain from hostname + const subdomain = hostname.replace('.datakit.page', ''); + + // Handle main domain (no subdomain or www) + if (subdomain === 'datakit' || subdomain === '' || subdomain === 'www') { + return proxyToFrontend(request, env.FRONTEND_URL); + } + + // Handle API subdomain + if (subdomain === 'api') { + return proxyToBackend(request, env.BACKEND_URL); + } + + // Handle share subdomain (fallback sharing) + if (subdomain === 'share') { + return handleShareSubdomain(request, env); + } + + // Handle reserved subdomains + if (RESERVED_SUBDOMAINS.has(subdomain)) { + return new Response('This subdomain is reserved', { + status: 403, + headers: { 'content-type': 'text/plain' } + }); + } + + // Handle custom project subdomains + return handleProjectSubdomain(subdomain, request, env); + }, +}; + +/** + * Proxy request to frontend app + */ +async function proxyToFrontend(request: Request, frontendUrl: string): Promise { + const url = new URL(request.url); + const targetUrl = `${frontendUrl}${url.pathname}${url.search}`; + + const modifiedRequest = new Request(targetUrl, { + method: request.method, + headers: request.headers, + body: request.body, + }); + + return fetch(modifiedRequest); +} + +/** + * Proxy request to backend API + */ +async function proxyToBackend(request: Request, backendUrl: string): Promise { + const url = new URL(request.url); + const targetUrl = `${backendUrl}${url.pathname}${url.search}`; + + const modifiedRequest = new Request(targetUrl, { + method: request.method, + headers: request.headers, + body: request.body, + }); + + return fetch(modifiedRequest); +} + +/** + * Handle share.datakit.page requests + */ +async function handleShareSubdomain(request: Request, env: Env): Promise { + const url = new URL(request.url); + + // Route /p/{shareId} to project preview + if (url.pathname.startsWith('/p/')) { + const shareId = url.pathname.split('/')[2]; + if (shareId) { + return handleProjectPreview(shareId, request, env, false); + } + } + + // For other paths, proxy to frontend + return proxyToFrontend(request, env.FRONTEND_URL); +} + +/** + * Handle custom project subdomains + */ +async function handleProjectSubdomain( + customSlug: string, + request: Request, + env: Env +): Promise { + // Validate slug format + if (!isValidSlug(customSlug)) { + return new Response('Invalid subdomain format', { + status: 400, + headers: { 'content-type': 'text/plain' } + }); + } + + return handleProjectPreview(customSlug, request, env, true); +} + +/** + * Handle project preview for both custom slugs and share IDs + */ +async function handleProjectPreview( + identifier: string, + request: Request, + env: Env, + isCustomSlug: boolean +): Promise { + const url = new URL(request.url); + + try { + // Fetch project data from backend + const apiUrl = `${env.BACKEND_URL}/project-sharing/preview/${identifier}`; + const apiResponse = await fetch(apiUrl, { + headers: { + 'User-Agent': 'DataKit-Worker/1.0', + 'Accept': 'application/json', + }, + }); + + if (!apiResponse.ok) { + if (apiResponse.status === 404) { + return generateNotFoundPage(identifier, isCustomSlug); + } + throw new Error(`API returned ${apiResponse.status}`); + } + + const projectData = await apiResponse.json(); + + // For API requests, return JSON + if (url.pathname.startsWith('/api/') || + request.headers.get('Accept')?.includes('application/json')) { + return new Response(JSON.stringify(projectData), { + headers: { + 'content-type': 'application/json', + 'cache-control': 'public, max-age=300', + 'access-control-allow-origin': env.ALLOWED_ORIGINS || '*', + }, + }); + } + + // For browser requests, return HTML + const html = generateProjectPreviewHTML(projectData, isCustomSlug); + + return new Response(html, { + headers: { + 'content-type': 'text/html; charset=utf-8', + 'cache-control': 'public, max-age=300', // 5 minutes + 'x-robots-tag': projectData.accessType === 'public' ? 'index, follow' : 'noindex, nofollow', + }, + }); + + } catch (error) { + console.error('Error handling project preview:', error); + return generateErrorPage(identifier, isCustomSlug); + } +} + +/** + * Generate HTML for project preview + */ +function generateProjectPreviewHTML(projectData: any, isCustomSlug: boolean): string { + const baseUrl = isCustomSlug ? + `https://${projectData.customSlug}.datakit.page` : + `https://share.datakit.page/p/${projectData.shareId}`; + + const title = projectData.settings?.customBranding?.title || + `${projectData.projectName} - DataKit`; + const description = projectData.settings?.customBranding?.description || + `Shared data project from ${projectData.workspaceName}`; + + return ` + + + + + ${escapeHtml(title)} + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

${escapeHtml(title)}

+

${escapeHtml(description)}

+ ${projectData.settings?.showOwnerInfo ? + `

Shared by ${escapeHtml(projectData.workspaceName)}

` : + ''} +
+ +
+
+
${projectData.fileCount}
+
Files
+
+
+
${projectData.totalSize}
+
Total Size
+
+
+
${formatDate(projectData.lastUpdated)}
+
Last Updated
+
+
+ + ${projectData.requireAuth ? ` +
+

🔒 Authentication Required

+

This project requires you to sign in to DataKit to access the data.

+
+ ` : ''} + + + + +
+ +`; +} + +/** + * Generate 404 page + */ +function generateNotFoundPage(identifier: string, isCustomSlug: boolean): Response { + const html = ` + + + + + Project Not Found - DataKit + + + +
+

404

+

The shared project "${escapeHtml(identifier)}" was not found.

+

It may have been removed or the link is incorrect.

+

Return to DataKit

+
+ +`; + + return new Response(html, { + status: 404, + headers: { 'content-type': 'text/html; charset=utf-8' } + }); +} + +/** + * Generate error page + */ +function generateErrorPage(identifier: string, isCustomSlug: boolean): Response { + const html = ` + + + + + Error Loading Project - DataKit + + + +
+

⚠️ Error

+

We encountered an error while loading this project.

+

Please try again later or return to DataKit.

+
+ +`; + + return new Response(html, { + status: 500, + headers: { 'content-type': 'text/html; charset=utf-8' } + }); +} + +// Utility functions +function isValidSlug(slug: string): boolean { + return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(slug) && slug.length >= 3 && slug.length <= 50; +} + +function escapeHtml(text: string): string { + const div = new Text(text); + return div.data + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function formatDate(dateString: string): string { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); +} \ No newline at end of file diff --git a/backend/workers/wrangler.toml b/backend/workers/wrangler.toml new file mode 100644 index 0000000..ea54902 --- /dev/null +++ b/backend/workers/wrangler.toml @@ -0,0 +1,24 @@ +name = "datakit-subdomain-router" +main = "subdomain-router.ts" +compatibility_date = "2024-01-15" + +[env.production] +vars = { + BACKEND_URL = "https://api.datakit.page", + FRONTEND_URL = "https://datakit.page", + ALLOWED_ORIGINS = "*" +} + +[env.staging] +vars = { + BACKEND_URL = "https://api-staging.datakit.page", + FRONTEND_URL = "https://staging.datakit.page", + ALLOWED_ORIGINS = "*" +} + +[env.development] +vars = { + BACKEND_URL = "http://localhost:3001", + FRONTEND_URL = "http://localhost:3000", + ALLOWED_ORIGINS = "*" +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c18f544..c8fbdb8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.5", "@types/d3": "^7.4.3", + "@types/pako": "^2.0.4", "@types/papaparse": "^5.3.15", "apache-arrow": "^20.0.0", "class-variance-authority": "^0.7.1", @@ -38,6 +39,7 @@ "idb-keyval": "^6.2.2", "jspdf": "^3.0.1", "lucide-react": "^0.507.0", + "pako": "^2.1.0", "papaparse": "^5.5.2", "posthog-js": "^1.261.7", "prismjs": "^1.30.0", @@ -3664,6 +3666,11 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==" + }, "node_modules/@types/papaparse": { "version": "5.3.15", "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz", @@ -10673,6 +10680,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "node_modules/papaparse": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index ac642cc..2da4657 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.5", "@types/d3": "^7.4.3", + "@types/pako": "^2.0.4", "@types/papaparse": "^5.3.15", "apache-arrow": "^20.0.0", "class-variance-authority": "^0.7.1", @@ -42,6 +43,7 @@ "idb-keyval": "^6.2.2", "jspdf": "^3.0.1", "lucide-react": "^0.507.0", + "pako": "^2.1.0", "papaparse": "^5.5.2", "posthog-js": "^1.261.7", "prismjs": "^1.30.0", diff --git a/frontend/src/components/auth/AuthModal.tsx b/frontend/src/components/auth/AuthModal.tsx index d38b425..80bf107 100644 --- a/frontend/src/components/auth/AuthModal.tsx +++ b/frontend/src/components/auth/AuthModal.tsx @@ -461,7 +461,7 @@ const AuthModal: React.FC = ({
  • - Create unlimited workspaces + Create unlimited projects
  • diff --git a/frontend/src/components/cloud/SaveToCloudModal.tsx b/frontend/src/components/cloud/SaveToCloudModal.tsx new file mode 100644 index 0000000..a9d52a0 --- /dev/null +++ b/frontend/src/components/cloud/SaveToCloudModal.tsx @@ -0,0 +1,553 @@ +import React, { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + X, + Plus, + HardDrive, + AlertCircle, + CheckCircle, + CloudOff, + ArrowLeft, + ArrowRight, + Upload, +} from 'lucide-react'; +import { Button } from '@/components/ui/Button'; +import { useCloudStore } from '@/store/cloudStore'; +import { useAppStore } from '@/store/appStore'; +import { useAuth } from '@/hooks/auth/useAuth'; +import { useAuthStore } from '@/store/authStore'; +import { useNotifications } from '@/hooks/useNotifications'; + +interface SaveToCloudModalProps { + isOpen: boolean; + onClose: () => void; + fileId: string; +} + +type Step = 'project' | 'options' | 'upload'; + +const STEPS: Step[] = ['project', 'options', 'upload']; + +const SaveToCloudModal: React.FC = ({ + isOpen, + onClose, + fileId, +}) => { + const { isAuthenticated } = useAuth(); + const { currentWorkspaceId } = useAuthStore(); + const { showSuccess, showError } = useNotifications(); + const { files } = useAppStore(); + const { + cloudProjects, + currentCloudProject, + storageStats, + isUploadingToCloud, + uploadProgress, + error, + loadWorkspaceProjects, + loadStorageStats, + createCloudProject, + saveToCloud, + formatStorageSize, + clearError, + } = useCloudStore(); + + // Wizard state + const [currentStep, setCurrentStep] = useState('project'); + + // Form state + const [selectedProjectId, setSelectedProjectId] = useState(''); + const [isCreatingProject, setIsCreatingProject] = useState(false); + const [newProjectName, setNewProjectName] = useState(''); + const [replaceIfExists, setReplaceIfExists] = useState(false); + const [keepVersionHistory, setKeepVersionHistory] = useState(false); + + const file = files.find((f) => f.id === fileId); + + useEffect(() => { + if (isOpen && isAuthenticated && currentWorkspaceId) { + loadWorkspaceProjects(currentWorkspaceId); + loadStorageStats(); + } + }, [isOpen, isAuthenticated, currentWorkspaceId]); + + useEffect(() => { + // Pre-select current cloud project or first available + if (cloudProjects.length > 0 && !selectedProjectId) { + const defaultProject = + currentCloudProject?.id || cloudProjects[0].id; + setSelectedProjectId(defaultProject); + } + }, [cloudProjects, currentCloudProject]); + + // Reset state when modal closes + useEffect(() => { + if (!isOpen) { + setTimeout(() => { + setCurrentStep('project'); + setSelectedProjectId(''); + setIsCreatingProject(false); + setNewProjectName(''); + setReplaceIfExists(false); + setKeepVersionHistory(false); + }, 300); + } + }, [isOpen]); + + const handleNext = () => { + const stepIndex = STEPS.indexOf(currentStep); + if (stepIndex < STEPS.length - 1) { + setCurrentStep(STEPS[stepIndex + 1]); + } + }; + + const handleBack = () => { + const stepIndex = STEPS.indexOf(currentStep); + if (stepIndex > 0) { + setCurrentStep(STEPS[stepIndex - 1]); + } + }; + + const handleCreateProject = async () => { + if (!newProjectName.trim() || !currentWorkspaceId) return; + + try { + const project = await createCloudProject(currentWorkspaceId, newProjectName); + setSelectedProjectId(project.id); + setIsCreatingProject(false); + setNewProjectName(''); + showSuccess( + 'Project Created', + `Created cloud project "${project.name}"` + ); + } catch (error) { + showError( + 'Failed to Create Project', + error instanceof Error ? error.message : 'Unknown error' + ); + } + }; + + const handleStartUpload = () => { + setCurrentStep('upload'); + handleSave(); + }; + + const handleSave = async () => { + if (!selectedProjectId || !file) return; + + try { + await saveToCloud(fileId, selectedProjectId, { + replaceIfExists, + keepVersionHistory, + }); + + showSuccess( + 'Saved to Cloud', + `${file.fileName} has been saved to cloud storage`, + { duration: 5000 } + ); + + onClose(); + } catch (error) { + showError( + 'Save Failed', + error instanceof Error ? error.message : 'Failed to save to cloud' + ); + } + }; + + const getStepProgress = () => { + const currentIndex = STEPS.indexOf(currentStep); + return ((currentIndex + 1) / STEPS.length) * 100; + }; + + const renderStepContent = () => { + switch (currentStep) { + case 'project': + return renderProjectStep(); + case 'options': + return renderOptionsStep(); + case 'upload': + return renderUploadStep(); + default: + return null; + } + }; + + if (!isOpen || !file) return null; + + const fileSize = + typeof file.fileSize === 'bigint' + ? Number(file.fileSize) + : file.fileSize || 0; + + const storageUsedPercent = storageStats + ? (parseInt(storageStats.totalStorageUsed) / + parseInt(storageStats.storageLimit)) * + 100 + : 0; + + const willExceedLimit = storageStats + ? parseInt(storageStats.totalStorageUsed) + fileSize > + parseInt(storageStats.storageLimit) + : false; + + const renderProjectStep = () => ( + +
    +

    + Select Project +

    +

    + Choose where to save {file.fileName} +

    +
    + + {/* Storage Stats */} + {storageStats && ( +
    +
    + Storage Usage + + {formatStorageSize(storageStats.totalStorageUsed)} /{' '} + {formatStorageSize(storageStats.storageLimit)} + +
    +
    +
    90 + ? 'bg-red-500' + : storageUsedPercent > 70 + ? 'bg-yellow-500' + : 'bg-gradient-to-r from-primary to-blue-500' + }`} + style={{ width: `${storageUsedPercent}%` }} + /> +
    + {willExceedLimit && ( +
    + + This file will exceed your storage limit +
    + )} +
    + )} + + {/* Project Selection */} +
    + {isCreatingProject ? ( +
    + setNewProjectName(e.target.value)} + placeholder="Project name..." + className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-primary/50" + autoFocus + /> +
    + + +
    +
    + ) : ( + <> + {cloudProjects.length === 0 ? ( +
    + +

    + No cloud projects yet +

    + +
    + ) : ( +
    +
    + {cloudProjects.map((project) => { + const isSelected = selectedProjectId === project.id; + + return ( + + ); + })} +
    + +
    + )} + + )} +
    + +
    + +
    + + ); + + const renderOptionsStep = () => ( + +
    +

    + Save Options +

    +

    + Configure how the file should be saved +

    +
    + +
    + + + +
    + +
    + + +
    +
    + ); + + const renderUploadStep = () => ( + +
    +

    + {isUploadingToCloud ? 'Uploading...' : 'Upload Complete!'} +

    +

    + {isUploadingToCloud + ? `Saving ${file.fileName} to cloud storage` + : `${file.fileName} has been saved successfully` + } +

    +
    + + {/* Upload Progress */} + {isUploadingToCloud && ( +
    +
    + Progress + + {Math.round(uploadProgress)}% + +
    +
    +
    +
    +
    + )} + + {/* File info */} +
    +
    +
    + +
    +
    +
    {file.fileName}
    +
    + Size: {formatStorageSize(fileSize)} +
    +
    +
    +
    + + {/* Error Message */} + {error && ( +
    + +

    {error}

    +
    + )} + + {!isUploadingToCloud && ( + + )} + + ); + + return ( + + + e.stopPropagation()} + > + {/* Header with Progress */} +
    + {/* Progress Bar */} +
    + +
    + + {/* Close Button */} + +
    + + {/* Content */} +
    + + {renderStepContent()} + +
    +
    +
    +
    + ); +}; + +export default SaveToCloudModal; diff --git a/frontend/src/components/data-grid/DataPreviewGrid.tsx b/frontend/src/components/data-grid/DataPreviewGrid.tsx index bf5dd7d..dab91a0 100644 --- a/frontend/src/components/data-grid/DataPreviewGrid.tsx +++ b/frontend/src/components/data-grid/DataPreviewGrid.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useCallback } from 'react'; -import { CheckCircle } from 'lucide-react'; +import { CheckCircle, CloudUpload } from 'lucide-react'; +import { Tooltip } from '../ui/Tooltip'; import { useAppStore } from '@/store/appStore'; import { useInspectorStore } from '@/store/inspectorStore'; import { selectActiveFile } from '@/store/selectors/appSelectors'; @@ -14,6 +15,11 @@ import { useCellFormatting } from './hooks/useCellFormatting'; import { useColumnSorting } from './hooks/useColumnSorting'; import { useCellInteraction } from './hooks/useCellInteraction'; import CellContextMenu from './CellContextMenu'; +import ShareModal from '@/components/sharing/ShareModal'; +import SaveToCloudModal from '@/components/cloud/SaveToCloudModal'; +import { useShareStore } from '@/store/shareStore'; +import { useCloudStore } from '@/store/cloudStore'; +import { useAuth } from '@/hooks/auth/useAuth'; interface DataPreviewGridProps { fileId?: string; @@ -24,6 +30,9 @@ const DataPreviewGrid: React.FC = ({ fileId, hideHeader = const activeFile = useAppStore(selectActiveFile); const { setActiveTab } = useAppStore(); const { openPanel, analyzeFile } = useInspectorStore(); + const { isAuthenticated } = useAuth(); + const { isShareModalOpen, shareModalFileId, openShareModal, closeShareModal } = useShareStore(); + const { isSaveToCloudModalOpen, saveToCloudFileId, openSaveToCloudModal, closeSaveToCloudModal } = useCloudStore(); // Use provided fileId or fall back to active file const targetFileId = fileId || activeFile?.id; @@ -125,6 +134,18 @@ const DataPreviewGrid: React.FC = ({ fileId, hideHeader = analyzeFile(activeFile.id, tableName); }, [activeFile, openPanel, analyzeFile]); + const handleShareClick = useCallback(() => { + if (!activeFile) return; + if (!isAuthenticated) return; // Let tooltip handle the messaging + openShareModal(activeFile.id); + }, [activeFile, isAuthenticated, openShareModal]); + + const handleSaveToCloudClick = useCallback(() => { + if (!activeFile) return; + if (!isAuthenticated) return; // Let tooltip handle the messaging + openSaveToCloudModal(activeFile.id); + }, [activeFile, isAuthenticated, openSaveToCloudModal]); + const renderHeader = () => { if (!activeFile && !isLoading) return null; @@ -188,6 +209,41 @@ const DataPreviewGrid: React.FC = ({ fileId, hideHeader =
    + + +
    + + {/* TODO: to be released in the next iterations */} + {/* */} + {/* Share Button - show for all users with tooltip for non-authenticated */} + {/* + + */} + + +
    {/* Source Type Panels */}
    @@ -86,7 +88,7 @@ export const DataSourceManager: React.FC = ({

    - Connect Cloud Sources + Connect Remote Sources

    {/* Provider logos - 4 smaller icons */} diff --git a/frontend/src/components/data-sources/SourceTypeSelector.tsx b/frontend/src/components/data-sources/SourceTypeSelector.tsx deleted file mode 100644 index aeef790..0000000 --- a/frontend/src/components/data-sources/SourceTypeSelector.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { LucideIcon } from 'lucide-react'; - -export interface SourceTypeOption { - type: string; - label: string; - icon: LucideIcon; - description: string; -} - -interface SourceTypeSelectorProps { - sourceTypes: SourceTypeOption[]; - activeType: string; - onTypeSelect: (type: string) => void; -} - -export const SourceTypeSelector: React.FC = ({ - sourceTypes, - activeType, - onTypeSelect -}) => { - return ( -
    - {sourceTypes.map((sourceType) => { - const Icon = sourceType.icon; - const isActive = activeType === sourceType.type; - - return ( - - ); - })} -
    - ); -}; \ No newline at end of file diff --git a/frontend/src/components/data-sources/index.ts b/frontend/src/components/data-sources/index.ts index 1fa9425..f8eea07 100644 --- a/frontend/src/components/data-sources/index.ts +++ b/frontend/src/components/data-sources/index.ts @@ -1,4 +1 @@ -export { DataSourceManager } from './DataSourceManager'; -export { SourceTypeSelector } from './SourceTypeSelector'; - -export type { SourceTypeOption } from './SourceTypeSelector'; +export { DataSourceManager } from './DataSourceManager'; \ No newline at end of file diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 7277f02..e094996 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -2,20 +2,22 @@ import React from 'react'; import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react'; import { DataSourceManager } from '@/components/data-sources'; -import { WorkspaceSelector } from '@/components/workspace/WorkspaceSelector'; +import { ProjectSelector } from '@/components/projects/ProjectSelector'; import { FileTreeView, - WorkspaceFile, -} from '@/components/workspace/FileTreeView'; + LocalProjectFile, +} from '@/components/projects/FileTreeView'; import { ThemeColorPicker } from '@/components/common/ThemeColorPicker'; import { useDuckDBStore } from '@/store/duckDBStore'; import useDirectFileImport from '@/hooks/useDirectFileImport'; import { useAppStore } from '@/store/appStore'; +import { useCloudStore } from '@/store/cloudStore'; import { motion, AnimatePresence } from 'framer-motion'; import usePopover from '@/hooks/usePopover'; import { useNotifications } from '@/hooks/useNotifications'; import UserMenu from '@/components/auth/UserMenu'; +import { CloudFilesList } from '@/components/projects/CloudFilesList'; import DuckDBIcon from '@/assets/duckdb.svg'; // Check for custom logo from environment variable or window object @@ -154,16 +156,22 @@ const Sidebar: React.FC = ({ onDataLoad }) => { // Get notifications const { showSuccess } = useNotifications(); - // Get workspace state from appStore + // Get local project state from appStore const { - workspaceFiles, - addFileToWorkspace, - removeFileFromWorkspace, - renameFileInWorkspace, + projectFiles, + addFileToProject, + removeFileFromProject, + renameFileInProject, files, setActiveFile, } = useAppStore(); + // Get cloud store state + const { currentCloudProject } = useCloudStore(); + + // Determine if cloud project is active + const isCloudActive = currentCloudProject !== null; + const { processFileStreaming, processFile, @@ -193,15 +201,15 @@ const Sidebar: React.FC = ({ onDataLoad }) => { // Skip if this is being called from workspace file selection if (result && !skipWorkspaceAdd) { const fileType = file.name.split('.').pop()?.toLowerCase() || 'txt'; - const newFile: WorkspaceFile = { + const newFile: LocalProjectFile = { id: `file-${Date.now()}`, name: file.name, - type: fileType as WorkspaceFile['type'], + type: fileType as LocalProjectFile['type'], size: file.size, lastModified: file.lastModified, handle: isFileSystemAccessSupported() ? handle : undefined, // Only store handle if supported }; - addFileToWorkspace(newFile); + addFileToProject(newFile); } return result; @@ -216,7 +224,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { // Add remote file to workspace if (result.isRemote) { - const newFile: WorkspaceFile = { + const newFile: LocalProjectFile = { id: `remote-${Date.now()}`, name: result.fileName, type: 'remote', @@ -224,7 +232,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { remoteUrl: result.remoteURL, lastModified: Date.now(), }; - addFileToWorkspace(newFile); + addFileToProject(newFile); // Show success notification for remote file import showSuccess( @@ -241,7 +249,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { // File tree handlers const handleFileRemove = async (fileId: string) => { // Get file info before removing - const file = workspaceFiles.find((f) => f.id === fileId); + const file = projectFiles.find((f) => f.id === fileId); if (!file) return; @@ -253,7 +261,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { if (!confirmed) return; // Remove from workspace after confirmation - removeFileFromWorkspace(fileId); + removeFileFromProject(fileId); // Show success notification showSuccess( @@ -284,10 +292,10 @@ const Sidebar: React.FC = ({ onDataLoad }) => { }; const handleFileRename = (fileId: string, newName: string) => { - renameFileInWorkspace(fileId, newName); + renameFileInProject(fileId, newName); }; - const handleFileSelect = async (file: WorkspaceFile) => { + const handleFileSelect = async (file: LocalProjectFile) => { console.log('[Sidebar] File selected from workspace:', file); if (!onDataLoad) { @@ -431,7 +439,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { await handleFileWithStreaming(selectedHandle, selectedFile, true); // Skip workspace add // Update the file handle in the workspace file for future automatic access - const updatedFile: WorkspaceFile = { + const updatedFile: LocalProjectFile = { ...file, handle: isFileSystemAccessSupported() ? selectedHandle @@ -505,14 +513,14 @@ const Sidebar: React.FC = ({ onDataLoad }) => { // Add file to workspace if (result) { const fileType = file.name.split('.').pop()?.toLowerCase() || 'txt'; - const newFile: WorkspaceFile = { + const newFile: LocalProjectFile = { id: `file-${Date.now()}`, name: file.name, - type: fileType as WorkspaceFile['type'], + type: fileType as LocalProjectFile['type'], size: file.size, lastModified: file.lastModified, }; - addFileToWorkspace(newFile); + addFileToProject(newFile); } return result; @@ -610,7 +618,7 @@ const Sidebar: React.FC = ({ onDataLoad }) => { {/* Workspace Selector */}
    - +
    {/* Data Source Manager section */} @@ -626,14 +634,14 @@ const Sidebar: React.FC = ({ onDataLoad }) => { if (result) { const fileType = file.name.split('.').pop()?.toLowerCase() || 'txt'; - const newFile: WorkspaceFile = { + const newFile: LocalProjectFile = { id: `file-${Date.now()}`, name: file.name, - type: fileType as WorkspaceFile['type'], + type: fileType as LocalProjectFile['type'], size: file.size, lastModified: file.lastModified, }; - addFileToWorkspace(newFile); + addFileToProject(newFile); } return result; @@ -645,13 +653,19 @@ const Sidebar: React.FC = ({ onDataLoad }) => { {/* Loading Status - Combined for both local and remote */}
    -
    - + + {/* Unified File Tree */} +
    + {isCloudActive ? ( + + ) : ( + + )}
    {/* File Tree - Main content area */} diff --git a/frontend/src/components/project-sharing/ShareProjectModal.tsx b/frontend/src/components/project-sharing/ShareProjectModal.tsx new file mode 100644 index 0000000..3b285c6 --- /dev/null +++ b/frontend/src/components/project-sharing/ShareProjectModal.tsx @@ -0,0 +1,276 @@ +import React, { useState, useEffect } from 'react'; +import { X } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + projectSharingService, + CreateProjectShareDto, + ShareAccessType, + SharePermission, + ProjectShare, +} from '@/lib/api/projectSharingService'; +import { useNotifications } from '@/hooks/useNotifications'; +import { useAuthStore } from '@/store/authStore'; +import { + ShareMethodStep, + PermissionsStep, + ReviewStep, + SuccessStep, +} from './components'; + +interface ShareProjectModalProps { + isOpen: boolean; + onClose: () => void; + projectId: string; + projectName: string; +} + +type ShareMethod = 'quick' | 'custom'; +type Step = 'method' | 'permissions' | 'review' | 'success'; + +const STEPS: Step[] = ['method', 'permissions', 'review', 'success']; + +export const ShareProjectModal: React.FC = ({ + isOpen, + onClose, + projectId, + projectName, +}) => { + const { showSuccess, showError } = useNotifications(); + const { currentWorkspace } = useAuthStore(); + + // Wizard state + const [currentStep, setCurrentStep] = useState('method'); + const [shareMethod, setShareMethod] = useState(null); + + // Form state + const [workspaceSlug, setWorkspaceSlug] = useState(''); + const [accessType, setAccessType] = useState(ShareAccessType.AUTHENTICATED); + const [allowedEmails, setAllowedEmails] = useState(''); + const [permissions, setPermissions] = useState([ + SharePermission.VIEW, + SharePermission.EXPORT, + ]); + const [expirationDays, setExpirationDays] = useState(7); + + // UI state + const [isSharing, setIsSharing] = useState(false); + const [currentShare, setCurrentShare] = useState(null); + const [copied, setCopied] = useState(false); + + // Initialize workspace slug from workspace settings or name + useEffect(() => { + if (currentWorkspace && !workspaceSlug) { + // Use existing workspace slug if available, otherwise generate from name + const slug = currentWorkspace.slug || currentWorkspace.name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + setWorkspaceSlug(slug); + } + }, [currentWorkspace, workspaceSlug]); + + // Reset state when modal closes + useEffect(() => { + if (!isOpen) { + setTimeout(() => { + setCurrentStep('method'); + setShareMethod(null); + setAllowedEmails(''); + setCurrentShare(null); + setCopied(false); + }, 300); + } + }, [isOpen]); + + const handleNext = () => { + const stepIndex = STEPS.indexOf(currentStep); + if (stepIndex < STEPS.length - 1) { + setCurrentStep(STEPS[stepIndex + 1]); + } + }; + + const handleBack = () => { + const stepIndex = STEPS.indexOf(currentStep); + if (stepIndex > 0) { + setCurrentStep(STEPS[stepIndex - 1]); + } + }; + + const handleMethodSelect = (method: ShareMethod) => { + setShareMethod(method); + // For now, only quick share is available + if (method === 'quick') { + setCurrentStep('permissions'); + } + }; + + const handleCreateShare = async () => { + setIsSharing(true); + try { + const dto: CreateProjectShareDto = { + projectId, + // Custom URL disabled for now + customSlug: undefined, + accessType, + allowedEmails: accessType === ShareAccessType.EMAIL_LIST && allowedEmails ? + allowedEmails.split(',').map(email => email.trim()).filter(email => email) : undefined, + requireAuth: accessType !== ShareAccessType.PUBLIC, + permissions, + expiresAt: expirationDays > 0 ? + new Date(Date.now() + expirationDays * 24 * 60 * 60 * 1000).toISOString() : undefined, + settings: { + showOwnerInfo: true, + allowDownload: permissions.includes(SharePermission.EXPORT), + allowQueryExecution: permissions.includes(SharePermission.QUERY), + allowAIUsage: permissions.includes(SharePermission.AI), + }, + }; + + const share = await projectSharingService.createProjectShare(dto); + setCurrentShare(share); + setCurrentStep('success'); + + showSuccess( + 'Project Shared!', + 'Your sharing link has been created successfully', + { icon: 'link', duration: 5000 } + ); + } catch (error) { + showError( + 'Share Failed', + error instanceof Error ? error.message : 'Failed to create share' + ); + } finally { + setIsSharing(false); + } + }; + + const handleCopyLink = async () => { + if (!currentShare) return; + + await projectSharingService.copyShareLink(currentShare.shareUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + + showSuccess('Link Copied!', 'Share link copied to clipboard', { + icon: 'copy', + duration: 3000, + }); + }; + + const getStepProgress = () => { + if (currentStep === 'success') return 100; + const currentIndex = STEPS.indexOf(currentStep); + const totalSteps = 3; // method, permissions, review (custom URL disabled) + return ((currentIndex + 1) / totalSteps) * 100; + }; + + const renderStepContent = () => { + switch (currentStep) { + case 'method': + return ( + + ); + + case 'permissions': + return ( + + ); + + case 'review': + return ( + + ); + + case 'success': + return currentShare ? ( + + ) : null; + + default: + return null; + } + }; + + if (!isOpen) return null; + + return ( + + + e.stopPropagation()} + > + {/* Header with Progress */} +
    + {/* Progress Bar */} +
    + +
    + + {/* Close Button */} + +
    + + {/* Content */} +
    + + {renderStepContent()} + +
    +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/src/components/project-sharing/components/PermissionsStep.tsx b/frontend/src/components/project-sharing/components/PermissionsStep.tsx new file mode 100644 index 0000000..ac76c98 --- /dev/null +++ b/frontend/src/components/project-sharing/components/PermissionsStep.tsx @@ -0,0 +1,245 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { ArrowLeft, ArrowRight, Eye, Download, Database, Sparkles, Check, Lock, Globe, Users } from 'lucide-react'; +import { Button } from '@/components/ui/Button'; +import { SharePermission, ShareAccessType } from '@/lib/api/projectSharingService'; + +interface PermissionsStepProps { + permissions: SharePermission[]; + onPermissionChange: (permissions: SharePermission[]) => void; + accessType: ShareAccessType; + onAccessTypeChange: (accessType: ShareAccessType) => void; + allowedEmails: string; + onAllowedEmailsChange: (emails: string) => void; + expirationDays: number; + onExpirationChange: (days: number) => void; + onBack: () => void; + onNext: () => void; +} + +export const PermissionsStep: React.FC = ({ + permissions, + onPermissionChange, + accessType, + onAccessTypeChange, + allowedEmails, + onAllowedEmailsChange, + expirationDays, + onExpirationChange, + onBack, + onNext, +}) => { + const handlePermissionToggle = (permission: SharePermission, isRequired = false) => { + if (isRequired) return; + + if (permissions.includes(permission)) { + onPermissionChange(permissions.filter(p => p !== permission)); + } else { + onPermissionChange([...permissions, permission]); + } + }; + + return ( + +
    +

    + Set permissions +

    +

    + Control what viewers can do with your data +

    +
    + + {/* Permission Cards */} +
    + {[ + { + id: SharePermission.VIEW, + icon: Eye, + title: 'View', + description: 'See data & charts', + required: true, + color: 'blue', + }, + { + id: SharePermission.EXPORT, + icon: Download, + title: 'Export', + description: 'Download files', + color: 'green', + }, + { + id: SharePermission.QUERY, + icon: Database, + title: 'Query', + description: 'Run SQL queries', + color: 'purple', + }, + { + id: SharePermission.AI, + icon: Sparkles, + title: 'AI Analysis', + description: 'Use AI features', + color: 'pink', + }, + ].map((perm) => ( + + ))} +
    + + {/* Access Level */} +
    + +
    + {/* All DataKit Users */} + + + {/* Specific DataKit Users */} + + + {/* Public Access */} + +
    +
    + + {/* Email Input for Specific Users */} + {accessType === ShareAccessType.EMAIL_LIST && ( +
    + +