From 448a21649e4c42c1410dc1ceca1c20511758554a Mon Sep 17 00:00:00 2001 From: anibilag Date: Sat, 28 Dec 2024 13:21:53 +0300 Subject: [PATCH] =?UTF-8?q?Frontend=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D1=81=20mock=20Backend=20=D0=B5=D1=89=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B6=D0=B5=D0=BD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 + package-lock.json | 2806 ++++++++++++++++- package.json | 15 +- prisma/schema.prisma | 27 +- server.zip | Bin 0 -> 17154 bytes server/config/logger.ts | 78 + server/index.js | 4 +- server/index.ts | 29 +- server/middleware/auth.js | 28 - server/middleware/auth.ts | 40 - server/middleware/auth/auth.ts | 29 + server/middleware/auth/extractToken.ts | 11 + server/middleware/auth/getUser.ts | 22 + server/middleware/auth/index.ts | 5 + server/middleware/auth/types.ts | 12 + server/middleware/auth/validateToken.ts | 10 + server/middleware/error/errorHandler.ts | 20 + server/middleware/error/errorLogger.ts | 27 + server/middleware/logging/requestLogger.ts | 21 + .../middleware/validation/validateRequest.ts | 17 + server/routes/articles.js | 91 - server/routes/articles/controllers/crud.ts | 123 + server/routes/articles/controllers/list.ts | 41 + server/routes/articles/controllers/search.ts | 44 + server/routes/articles/crud.ts | 93 + server/routes/articles/index.ts | 19 + server/routes/articles/list.ts | 41 + server/routes/articles/search.ts | 44 + server/routes/auth.js | 38 - server/routes/auth.ts | 6 +- server/routes/auth/controllers/auth.ts | 36 + server/routes/auth/index.ts | 12 + server/routes/auth/validation/authSchemas.ts | 8 + server/routes/gallery/controllers/crud.ts | 81 + server/routes/gallery/index.ts | 19 + server/routes/images/index.ts | 58 + server/routes/users.js | 93 - server/routes/users.ts | 12 +- server/routes/users/controllers/users.ts | 29 + server/routes/users/index.ts | 10 + server/services/authService.ts | 114 +- server/services/galleryService.ts | 92 + server/services/s3Service.ts | 81 + server/utils/permissions.ts | 16 + src/App.tsx | 4 +- src/components/ArticleCard.tsx | 1 - src/components/BookmarkButton.tsx | 40 + src/components/FeaturedSection.tsx | 2 +- src/components/Footer/DesignStudioLogo.tsx | 16 + .../{Footer.tsx => Footer/index.tsx} | 11 +- src/components/GalleryManager.tsx | 2 +- src/components/GalleryManager/GalleryGrid.tsx | 1 - src/components/GalleryManager/ImageForm.tsx | 1 - src/components/GalleryManager/index.tsx | 2 +- src/components/Header/CitySelector.tsx | 47 + src/components/Header/Logo.tsx | 11 + src/components/Header/MobileMenu.tsx | 74 + src/components/Header/Navigation.tsx | 39 + src/components/Header/SearchBar.tsx | 40 + src/components/Header/index.tsx | 94 + src/components/ImageUpload/ImageDropzone.tsx | 45 + src/components/ImageUpload/ImageUploader.tsx | 81 + .../ImageUpload/ResolutionSelect.tsx | 36 + src/components/ImageUpload/UploadProgress.tsx | 56 + src/components/Pagination.tsx | 1 - src/components/ReactionButtons.tsx | 3 +- src/config/imageResolutions.ts | 28 + src/data/mock.ts | 4 +- src/hooks/useGallery.ts | 94 + src/pages/ArticlePage.tsx | 2 +- src/pages/BookmarksPage.tsx | 38 + src/pages/HomePage.tsx | 1 - src/pages/LoginPage.tsx | 14 +- src/pages/SearchPage.tsx | 2 +- src/services/galleryService.ts | 27 + src/services/imageService.ts | 32 + src/stores/bookmarkStore.ts | 36 + src/types/image.ts | 21 + tsconfig.json | 9 +- vite.config.ts | 1 + 80 files changed, 4922 insertions(+), 399 deletions(-) create mode 100644 .env create mode 100644 server.zip create mode 100644 server/config/logger.ts delete mode 100644 server/middleware/auth.js delete mode 100644 server/middleware/auth.ts create mode 100644 server/middleware/auth/auth.ts create mode 100644 server/middleware/auth/extractToken.ts create mode 100644 server/middleware/auth/getUser.ts create mode 100644 server/middleware/auth/index.ts create mode 100644 server/middleware/auth/types.ts create mode 100644 server/middleware/auth/validateToken.ts create mode 100644 server/middleware/error/errorHandler.ts create mode 100644 server/middleware/error/errorLogger.ts create mode 100644 server/middleware/logging/requestLogger.ts create mode 100644 server/middleware/validation/validateRequest.ts delete mode 100644 server/routes/articles.js create mode 100644 server/routes/articles/controllers/crud.ts create mode 100644 server/routes/articles/controllers/list.ts create mode 100644 server/routes/articles/controllers/search.ts create mode 100644 server/routes/articles/crud.ts create mode 100644 server/routes/articles/index.ts create mode 100644 server/routes/articles/list.ts create mode 100644 server/routes/articles/search.ts delete mode 100644 server/routes/auth.js create mode 100644 server/routes/auth/controllers/auth.ts create mode 100644 server/routes/auth/index.ts create mode 100644 server/routes/auth/validation/authSchemas.ts create mode 100644 server/routes/gallery/controllers/crud.ts create mode 100644 server/routes/gallery/index.ts create mode 100644 server/routes/images/index.ts delete mode 100644 server/routes/users.js create mode 100644 server/routes/users/controllers/users.ts create mode 100644 server/routes/users/index.ts create mode 100644 server/services/galleryService.ts create mode 100644 server/services/s3Service.ts create mode 100644 server/utils/permissions.ts create mode 100644 src/components/BookmarkButton.tsx create mode 100644 src/components/Footer/DesignStudioLogo.tsx rename src/components/{Footer.tsx => Footer/index.tsx} (93%) create mode 100644 src/components/Header/CitySelector.tsx create mode 100644 src/components/Header/Logo.tsx create mode 100644 src/components/Header/MobileMenu.tsx create mode 100644 src/components/Header/Navigation.tsx create mode 100644 src/components/Header/SearchBar.tsx create mode 100644 src/components/Header/index.tsx create mode 100644 src/components/ImageUpload/ImageDropzone.tsx create mode 100644 src/components/ImageUpload/ImageUploader.tsx create mode 100644 src/components/ImageUpload/ResolutionSelect.tsx create mode 100644 src/components/ImageUpload/UploadProgress.tsx create mode 100644 src/config/imageResolutions.ts create mode 100644 src/hooks/useGallery.ts create mode 100644 src/pages/BookmarksPage.tsx create mode 100644 src/services/galleryService.ts create mode 100644 src/services/imageService.ts create mode 100644 src/stores/bookmarkStore.ts create mode 100644 src/types/image.ts diff --git a/.env b/.env new file mode 100644 index 0000000..1665982 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +DATABASE_URL="postgresql://lenin:8D2v7A4s@max.anibilag.ru:5466/russcult" +JWT_SECRET="d131c955dce9f6709acb06f2680b2916ac641f91b814bb0bd28872f9b1edc949" +PORT=5000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e4fe8b6..4d4d085 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "vite-react-typescript-starter", "version": "0.0.0", "dependencies": { + "@aws-sdk/client-s3": "^3.525.0", + "@aws-sdk/s3-request-presigner": "^3.525.0", "@prisma/client": "^5.10.2", "@tiptap/pm": "^2.2.4", "@tiptap/react": "^2.2.4", @@ -16,18 +18,27 @@ "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.18.3", + "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.344.0", + "multer": "1.4.5-lts.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-dropzone": "^14.2.3", "react-router-dom": "^6.22.3", + "sharp": "^0.33.2", + "uuid": "^9.0.1", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^5.0.0", "zustand": "^4.5.1" }, "devDependencies": { "@eslint/js": "^9.9.1", + "@types/multer": "^1.4.11", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/uuid": "^9.0.8", + "@types/winston": "^2.4.4", "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.18", "eslint": "^9.9.1", @@ -67,6 +78,953 @@ "node": ">=6.0.0" } }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "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==", + "license": "Apache-2.0", + "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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "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/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "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==", + "license": "Apache-2.0", + "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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.712.0.tgz", + "integrity": "sha512-Hq1IIwOFutmHtTz3mROR1XhTDL8rxcYbYw3ajjgeMJB5tjcvodpfkfz/L4dxXZMwqylWf6SNQNAiaGh5mlsGGQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.712.0", + "@aws-sdk/client-sts": "3.712.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/credential-provider-node": "3.712.0", + "@aws-sdk/middleware-bucket-endpoint": "3.709.0", + "@aws-sdk/middleware-expect-continue": "3.709.0", + "@aws-sdk/middleware-flexible-checksums": "3.709.0", + "@aws-sdk/middleware-host-header": "3.709.0", + "@aws-sdk/middleware-location-constraint": "3.709.0", + "@aws-sdk/middleware-logger": "3.709.0", + "@aws-sdk/middleware-recursion-detection": "3.709.0", + "@aws-sdk/middleware-sdk-s3": "3.709.0", + "@aws-sdk/middleware-ssec": "3.709.0", + "@aws-sdk/middleware-user-agent": "3.709.0", + "@aws-sdk/region-config-resolver": "3.709.0", + "@aws-sdk/signature-v4-multi-region": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-endpoints": "3.709.0", + "@aws-sdk/util-user-agent-browser": "3.709.0", + "@aws-sdk/util-user-agent-node": "3.712.0", + "@aws-sdk/xml-builder": "3.709.0", + "@smithy/config-resolver": "^3.0.13", + "@smithy/core": "^2.5.5", + "@smithy/eventstream-serde-browser": "^3.0.14", + "@smithy/eventstream-serde-config-resolver": "^3.0.11", + "@smithy/eventstream-serde-node": "^3.0.13", + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/hash-blob-browser": "^3.1.10", + "@smithy/hash-node": "^3.0.11", + "@smithy/hash-stream-node": "^3.1.10", + "@smithy/invalid-dependency": "^3.0.11", + "@smithy/md5-js": "^3.0.11", + "@smithy/middleware-content-length": "^3.0.13", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/middleware-retry": "^3.0.30", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.30", + "@smithy/util-defaults-mode-node": "^3.0.30", + "@smithy/util-endpoints": "^2.1.7", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "@smithy/util-stream": "^3.3.2", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.712.0.tgz", + "integrity": "sha512-tBo/eW3YpZ9f3Q1qA7aA8uliNFJJX0OP7R2IUJ8t6rqVTk15wWCEPNmXzUZKgruDnKUfCaF4+r9q/Yy4fBc9PA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/middleware-host-header": "3.709.0", + "@aws-sdk/middleware-logger": "3.709.0", + "@aws-sdk/middleware-recursion-detection": "3.709.0", + "@aws-sdk/middleware-user-agent": "3.709.0", + "@aws-sdk/region-config-resolver": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-endpoints": "3.709.0", + "@aws-sdk/util-user-agent-browser": "3.709.0", + "@aws-sdk/util-user-agent-node": "3.712.0", + "@smithy/config-resolver": "^3.0.13", + "@smithy/core": "^2.5.5", + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/hash-node": "^3.0.11", + "@smithy/invalid-dependency": "^3.0.11", + "@smithy/middleware-content-length": "^3.0.13", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/middleware-retry": "^3.0.30", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.30", + "@smithy/util-defaults-mode-node": "^3.0.30", + "@smithy/util-endpoints": "^2.1.7", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.712.0.tgz", + "integrity": "sha512-xNFrG9syrG6pxUP7Ld/nu3afQ9+rbJM9qrE+wDNz4VnNZ3vLiJty4fH85zBFhOQ5OF2DIJTWsFzXGi2FYjsCMA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/credential-provider-node": "3.712.0", + "@aws-sdk/middleware-host-header": "3.709.0", + "@aws-sdk/middleware-logger": "3.709.0", + "@aws-sdk/middleware-recursion-detection": "3.709.0", + "@aws-sdk/middleware-user-agent": "3.709.0", + "@aws-sdk/region-config-resolver": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-endpoints": "3.709.0", + "@aws-sdk/util-user-agent-browser": "3.709.0", + "@aws-sdk/util-user-agent-node": "3.712.0", + "@smithy/config-resolver": "^3.0.13", + "@smithy/core": "^2.5.5", + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/hash-node": "^3.0.11", + "@smithy/invalid-dependency": "^3.0.11", + "@smithy/middleware-content-length": "^3.0.13", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/middleware-retry": "^3.0.30", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.30", + "@smithy/util-defaults-mode-node": "^3.0.30", + "@smithy/util-endpoints": "^2.1.7", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.712.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.712.0.tgz", + "integrity": "sha512-gIO6BD+hkEe3GKQhbiFP0zcNQv0EkP1Cl9SOstxS+X9CeudEgVX/xEPUjyoFVkfkntPBJ1g0I1u5xOzzRExl4g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.712.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/credential-provider-node": "3.712.0", + "@aws-sdk/middleware-host-header": "3.709.0", + "@aws-sdk/middleware-logger": "3.709.0", + "@aws-sdk/middleware-recursion-detection": "3.709.0", + "@aws-sdk/middleware-user-agent": "3.709.0", + "@aws-sdk/region-config-resolver": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-endpoints": "3.709.0", + "@aws-sdk/util-user-agent-browser": "3.709.0", + "@aws-sdk/util-user-agent-node": "3.712.0", + "@smithy/config-resolver": "^3.0.13", + "@smithy/core": "^2.5.5", + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/hash-node": "^3.0.11", + "@smithy/invalid-dependency": "^3.0.11", + "@smithy/middleware-content-length": "^3.0.13", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/middleware-retry": "^3.0.30", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.30", + "@smithy/util-defaults-mode-node": "^3.0.30", + "@smithy/util-endpoints": "^2.1.7", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.709.0.tgz", + "integrity": "sha512-7kuSpzdOTAE026j85wq/fN9UDZ70n0OHw81vFqMWwlEFtm5IQ/MRCLKcC4HkXxTdfy1PqFlmoXxWqeBa15tujw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/core": "^2.5.5", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/signature-v4": "^4.2.4", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/util-middleware": "^3.0.11", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.709.0.tgz", + "integrity": "sha512-ZMAp9LSikvHDFVa84dKpQmow6wsg956Um20cKuioPpX2GGreJFur7oduD+tRJT6FtIOHn+64YH+0MwiXLhsaIQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/property-provider": "^3.1.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.709.0.tgz", + "integrity": "sha512-lIS7XLwCOyJnLD70f+VIRr8DNV1HPQe9oN6aguYrhoczqz7vDiVZLe3lh714cJqq9rdxzFypK5DqKHmcscMEPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/property-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/util-stream": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.712.0.tgz", + "integrity": "sha512-sTsdQ/Fm/suqMdpjhMuss/5uKL18vcuWnNTQVrG9iGNRqZLbq65MXquwbUpgzfoUmIcH+4CrY6H2ebpTIECIag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/credential-provider-env": "3.709.0", + "@aws-sdk/credential-provider-http": "3.709.0", + "@aws-sdk/credential-provider-process": "3.709.0", + "@aws-sdk/credential-provider-sso": "3.712.0", + "@aws-sdk/credential-provider-web-identity": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/credential-provider-imds": "^3.2.8", + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.712.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.712.0.tgz", + "integrity": "sha512-gXrHymW3rMRYORkPVQwL8Gi5Lu92F16SoZR543x03qCi7rm00oL9tRD85ACxkhprS1Wh8lUIUMNoeiwnYWTNuQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.709.0", + "@aws-sdk/credential-provider-http": "3.709.0", + "@aws-sdk/credential-provider-ini": "3.712.0", + "@aws-sdk/credential-provider-process": "3.709.0", + "@aws-sdk/credential-provider-sso": "3.712.0", + "@aws-sdk/credential-provider-web-identity": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/credential-provider-imds": "^3.2.8", + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.709.0.tgz", + "integrity": "sha512-IAC+jPlGQII6jhIylHOwh3RgSobqlgL59nw2qYTURr8hMCI0Z1p5y2ee646HTVt4WeCYyzUAXfxr6YI/Vitv+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.712.0.tgz", + "integrity": "sha512-8lCMxY7Lb9VK9qdlNXRJXE3W1UDVURnJZ3a4XWYNY6yr1TfQaN40mMyXX1oNlXXJtMV0szRvjM8dZj37E/ESAw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.712.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/token-providers": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.709.0.tgz", + "integrity": "sha512-2lbDfE0IQ6gma/7BB2JpkjW5G0wGe4AS0x80oybYAYYviJmUtIR3Cn2pXun6bnAWElt4wYKl4su7oC36rs5rNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/property-provider": "^3.1.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.709.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.709.0.tgz", + "integrity": "sha512-03+tJOd7KIZOiqWH7Z8BOfQIWkKJgjcpKOJKZ6FR2KjWGUOE1G+bo11wF4UuHQ0RmpKnApt+pQghZmSnE7WEeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.709.0.tgz", + "integrity": "sha512-Tbl/DFvE4rHl8lMb9IzetwK4tf5R3VeHZkvEXQalsWoK0tbEQ8kXWi7wAYO4qbE7bFVvaxKX+irjJjTxf3BrCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.709.0.tgz", + "integrity": "sha512-wbYm9tkyCaqMeU82yjaXw7V5BxCSlSLNupENW63LC7Fvyo/aQzj6LjSMHcBpR2QwjBEhXCtF47L7aQ8SPTNhdw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-stream": "^3.3.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.709.0.tgz", + "integrity": "sha512-8gQYCYAaIw4lOCd5WYdf15Y/61MgRsAnrb2eiTl+icMlUOOzl8aOl5iDwm/Idp0oHZTflwxM4XSvGXO83PRWcw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.709.0.tgz", + "integrity": "sha512-5YQWPXfZq7OE0jB2G0PP8K10GBod/YPJXb+1CfJS6FbQaglRoIm8KZmVEvJNnptSKyGtE62veeCcCQcfAUfFig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.709.0.tgz", + "integrity": "sha512-jDoGSccXv9zebnpUoisjWd5u5ZPIalrmm6TjvPzZ8UqzQt3Beiz0tnQwmxQD6KRc7ADweWP5Ntiqzbw9xpVajg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.709.0.tgz", + "integrity": "sha512-PObL/wLr4lkfbQ0yXUWaoCWu/jcwfwZzCjsUiXW/H6hW9b/00enZxmx7OhtJYaR6xmh/Lcx5wbhIoDCbzdv0tw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.709.0.tgz", + "integrity": "sha512-FwtOG9t9xsLoLOQZ6qAdsWOjx9dsO6t28IjIDV1l6Ixiu2oC0Yks7goONjJUH0IDE4pDDDGzmuq0sn1XtHhheA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/core": "^2.5.5", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/signature-v4": "^4.2.4", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-stream": "^3.3.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.709.0.tgz", + "integrity": "sha512-2muiLe7YkmlwZp2SKz+goZrDThGfRq3o0FcJF3Puc0XGmcEPEDjih537mCoTrGgcXNFlBc7YChd84r3t72ySaQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.709.0.tgz", + "integrity": "sha512-ooc9ZJvgkjPhi9q05XwSfNTXkEBEIfL4hleo5rQBKwHG3aTHvwOM7LLzhdX56QZVa6sorPBp6fwULuRDSqiQHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-endpoints": "3.709.0", + "@smithy/core": "^2.5.5", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.709.0.tgz", + "integrity": "sha512-/NoCAMEVKAg3kBKOrNtgOfL+ECt6nrl+L7q2SyYmrcY4tVCmwuECVqewQaHc03fTnJijfKLccw0Fj+6wOCnB6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.712.0.tgz", + "integrity": "sha512-LE+uNtGDyypRMxBfrJmkpWaW+x0QFp4qYH+nZYMDLdD0um8UrTrbVSfvIxcVm9QsL1gVy6WkpUj+5cU3YZBgyQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@aws-sdk/util-format-url": "3.709.0", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/protocol-http": "^4.1.8", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.709.0.tgz", + "integrity": "sha512-m0vhJEy6SLbjL11K9cHzX/ZhCIj//1GkTbYk2d4tTQFSuPyJEkjmoeHk9dYm2mJy0wH48j29OJadI1JUsR5bOw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/signature-v4": "^4.2.4", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.709.0.tgz", + "integrity": "sha512-q5Ar6k71nci43IbULFgC8a89d/3EHpmd7HvBzqVGRcHnoPwh8eZDBfbBXKH83NGwcS1qPSRYiDbVfeWPm4/1jA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.709.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.709.0.tgz", + "integrity": "sha512-ArtLTMxgjf13Kfu3gWH3Ez9Q5TkDdcRZUofpKH3pMGB/C6KAbeSCtIIDKfoRTUABzyGlPyCrZdnFjKyH+ypIpg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz", + "integrity": "sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.709.0.tgz", + "integrity": "sha512-Mbc7AtL5WGCTKC16IGeUTz+sjpC3ptBda2t0CcK0kMVw3THDdcSq6ZlNKO747cNqdbwUvW34oHteUiHv4/z88Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/types": "^3.7.2", + "@smithy/util-endpoints": "^2.1.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.709.0.tgz", + "integrity": "sha512-HGR11hx1KeFfoub/TACf+Yyal37lR85791Di2QPaElQThaqztLlppxale3EohKboOFf7Q/zvslJyM0fmgrlpQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz", + "integrity": "sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.709.0.tgz", + "integrity": "sha512-/rL2GasJzdTWUURCQKFldw2wqBtY4k4kCiA2tVZSKg3y4Ey7zO34SW8ebaeCE2/xoWOyLR2/etdKyphoo4Zrtg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.709.0", + "@smithy/types": "^3.7.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.712.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.712.0.tgz", + "integrity": "sha512-26X21bZ4FWsVpqs33uOXiB60TOWQdVlr7T7XONDFL/XN7GEpUJkWuuIB4PTok6VOmh1viYcdxZQqekXPuzXexQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.709.0", + "@aws-sdk/types": "3.709.0", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.709.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.709.0.tgz", + "integrity": "sha512-2GPCwlNxeHspoK/Mc8nbk9cBOkSpp3j2SJUQmFnyQK6V/pR6II2oPRyZkMomug1Rc10hqlBHByMecq4zhV2uUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", @@ -358,6 +1316,36 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -898,6 +1886,367 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1300,6 +2649,696 @@ "win32" ] }, + "node_modules/@smithy/abort-controller": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.9.tgz", + "integrity": "sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", + "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", + "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.13.tgz", + "integrity": "sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.5.tgz", + "integrity": "sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-stream": "^3.3.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz", + "integrity": "sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz", + "integrity": "sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz", + "integrity": "sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz", + "integrity": "sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz", + "integrity": "sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz", + "integrity": "sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.10", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz", + "integrity": "sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz", + "integrity": "sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^4.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.1", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.11.tgz", + "integrity": "sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz", + "integrity": "sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz", + "integrity": "sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.11.tgz", + "integrity": "sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz", + "integrity": "sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz", + "integrity": "sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.5", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "3.0.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz", + "integrity": "sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/service-error-classification": "^3.0.11", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz", + "integrity": "sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz", + "integrity": "sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz", + "integrity": "sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz", + "integrity": "sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.11.tgz", + "integrity": "sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz", + "integrity": "sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz", + "integrity": "sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", + "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", + "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.5.0.tgz", + "integrity": "sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.5", + "@smithy/middleware-endpoint": "^3.2.5", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-stream": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.11.tgz", + "integrity": "sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz", + "integrity": "sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz", + "integrity": "sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^3.0.13", + "@smithy/credential-provider-imds": "^3.2.8", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.5.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz", + "integrity": "sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", + "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.2.tgz", + "integrity": "sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.1.2", + "@smithy/node-http-handler": "^3.3.2", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.2.0.tgz", + "integrity": "sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@tiptap/core": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", @@ -1701,12 +3740,66 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1732,13 +3825,28 @@ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "undici-types": "~6.19.8" } @@ -1749,6 +3857,20 @@ "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", "devOptional": true }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "18.3.11", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", @@ -1768,11 +3890,58 @@ "@types/react": "*" } }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "winston": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", @@ -2114,6 +4283,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2130,11 +4305,26 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -2241,6 +4431,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2300,6 +4496,23 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2414,11 +4627,23 @@ "node": ">= 6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -2426,8 +4651,55 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2455,6 +4727,21 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2493,6 +4780,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -2606,6 +4899,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2660,6 +4962,12 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3018,9 +5326,10 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -3041,7 +5350,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -3056,6 +5365,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -3116,6 +5429,28 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -3125,6 +5460,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3137,6 +5478,27 @@ "node": ">=16.0.0" } }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3214,6 +5576,12 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -3557,6 +5925,12 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3623,6 +5997,24 @@ "node": ">=0.12.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3772,6 +6164,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3864,6 +6262,23 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4001,6 +6416,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4010,11 +6434,50 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -4094,7 +6557,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -4121,6 +6583,15 @@ "node": ">= 0.8" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4246,9 +6717,10 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.0", @@ -4469,6 +6941,23 @@ "fsevents": "2.3.3" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/prosemirror-changeset": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", @@ -4770,6 +7259,29 @@ "react": "^18.3.1" } }, + "node_modules/react-dropzone": { + "version": "14.3.5", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.5.tgz", + "integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -4818,6 +7330,27 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4948,6 +7481,15 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5049,6 +7591,57 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5099,6 +7692,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5108,6 +7710,15 @@ "node": ">=0.10.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5116,6 +7727,29 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5224,6 +7858,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -5307,6 +7947,12 @@ "node": ">=14.0.0" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5371,6 +8017,15 @@ "node": ">=0.6" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5389,6 +8044,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5413,6 +8074,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -5458,9 +8125,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/unpipe": { "version": "1.0.0", @@ -5520,8 +8185,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -5531,6 +8195,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5618,6 +8295,88 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -5748,6 +8507,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 00ed8be..2974455 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "server": "node server/index.js" }, "dependencies": { + "@aws-sdk/client-s3": "^3.525.0", + "@aws-sdk/s3-request-presigner": "^3.525.0", "@prisma/client": "^5.10.2", "@tiptap/pm": "^2.2.4", "@tiptap/react": "^2.2.4", @@ -19,18 +21,27 @@ "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.18.3", + "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.344.0", + "multer": "1.4.5-lts.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-dropzone": "^14.2.3", "react-router-dom": "^6.22.3", + "sharp": "^0.33.2", + "uuid": "^9.0.1", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^5.0.0", "zustand": "^4.5.1" }, "devDependencies": { "@eslint/js": "^9.9.1", + "@types/multer": "^1.4.11", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/uuid": "^9.0.8", + "@types/winston": "^2.4.4", "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.18", "eslint": "^9.9.1", @@ -44,4 +55,4 @@ "typescript-eslint": "^8.3.0", "vite": "^5.4.2" } -} \ No newline at end of file +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8970f67..b7945bb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,7 +20,7 @@ model User { } model Article { - id String @id @default(uuid()) + id String @id @default(uuid()) title String excerpt String content String @@ -28,11 +28,28 @@ model Article { city String coverImage String readTime Int - likes Int @default(0) - dislikes Int @default(0) - publishedAt DateTime @default(now()) - author User @relation(fields: [authorId], references: [id]) + likes Int @default(0) + dislikes Int @default(0) + publishedAt DateTime @default(now()) + author User @relation(fields: [authorId], references: [id]) authorId String + gallery GalleryImage[] +} + +model GalleryImage { + id String @id @default(uuid()) + url String + caption String + alt String + width Int + height Int + size Int + format String + article Article @relation(fields: [articleId], references: [id], onDelete: Cascade) + articleId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + order Int @default(0) } model UserReaction { diff --git a/server.zip b/server.zip new file mode 100644 index 0000000000000000000000000000000000000000..0e9086af4178719bf38edb452a01f2bf25a2c93c GIT binary patch literal 17154 zcmb_j1yq#l)*iaMySq!erBk|*?(R|)5JWnqkq&7DDUlRGky4aYq`Q$2{=swbfM*81 z_rJIpg|&S4xBGebyFXP0C}?cJ`Io&)gzk@j{QUt3AP2ZvxO!T+vUzwT0U)5yfB)k{ zQv(G6_vWFVReHOg)%O}bE7cz$@BsLKfuQ{ZV&iCT;mvC61`aH#_Af9G)jr1{cJ$^Q z9KWLASjso~h4h;Fcf@k@$k1<+EF;?6ImF2a-`zM8r?B3_5CExj+D3$}40Jd#kXEHcpJ`Vjv z;*K6+^*iPEcJ9?noubRKV@;85h$s5%DS2;A?^h2lGwW-iOQ3-?R`uZzIlz7;! z8Q)}{r$cITuTq>MaDv~6HA-gc!BBoBo>{oV&0i6T+_v+!B;|c#o~%iWtzj>lNtj$o zei+Tv>E5O^1M+hBS)Vp~_BaY@X^Fh9fK4&Udq^AnICEzr8{pP2oq>Ly?Hb@A# z{5)H6Cipech&y%yv2z;stq`gTaPX+cG~b9J004JL0N_6`ygOLKKly3+w~p=X$S)2U z{2z~KNXW6Jqx*#@*`tYRN+cI|#>P`pf1X^(V_4(%tZ5}1&wBfs0%ibDX3UQdGc)^* zVFLr_B&Cu8v(-$X__$j^wFh5wo}yzjQWRIz64t!~m?sXJ^1JlKlkD|dycma@LV zRxAPkQ!8j? zgc;XesQyfZ%HB#T6UHtgF;ghZ&?HXM2J@8O|6-OHy?{!m#M z`l{VWE3RR=R;%&8e{qgy;d^oJkF)GI2PqVg-J7Qwr}&WKOFr^(yI|a_Kk`l#_u}3O zg++Tsyx%Om-Ca%0+%=r+EF7=# zU&G)2bBN-IYG+56I!>P{bfu&~6-$6*w7nTh-)sVj+os%YhQRV=oo_0H+nsCY%&)Ft zFA4^2)f#>lMx}PuQPq7TrCD1usmo=#w(W*#I?QB<2bG@}jXpa{DX&5K=a5KYvtSg% zP#IY6e(5q)*H3XG7`f8sNy)X?wj@xm=CxI9HML^dS;GBcdp{<>yuna-LM40>V>tt3 ze$>m6YGu20+%NltUGQwIXNAx<-ZI4HX6B+(#fh<+k;OZ_b8AW3wxKoxvxOOGG4NH6 z0=%`XEZjAL?duvtT?D$Js<`cWpdaJ7J%Va`A?U!cJEE{egNEKqd#lRA%S*~u?X0DG z_Hi9~t*ND_l}tI2uhzt7zp}v2kid300^PcxeP(FzhZ}8a0ad+d63x9OCH*oVKM0^w zKSS^>7NM@gt~#W7RZRC$ey? z;i?tUl%lMgQYX4*Pll^n861-d2@vRUy-0~VRflp>q~Itvw>2ryC_FTthl_{Z=MTp? z>1~cafwmhlQpI$8H*V3EZ-s+1jCOALftD5oM4-`^t~ERvc*FnI=fT&PoWJ8!p^UzV zjqz5ImMk;NYtvcCVK+l_U zhd(|!W^~3zPU4+=3Q(Jx;wjD1?8~~>p%7*1i`-ovU`2GB0}||V+%5jJ;Ar-9pxqZke()v3c*{Z7wBJzr>x_9q z=U}?dF8aD`MIiLI4Mc&(kta@>9}*JH#HSEBJ!#q}1jmP@CW7qo;idzk4VnBVag>cY z?x;VDE2pe*Jwt`^?YGbx*NCXGb~h>4qfg1}S}pq$64@fPV(b`L_6RZn0PnQ}{W#B3 z@OwU?X}kxS$-{6%`6;IA$gzyL9hWA4dn{zAcJ}-GqOK=N(s_DAg1DWld`F{&O>Wo` zZ0Ba`ObUBh4Kxw_&v*y^@UNba57JB)uC7l1A9f7d_?tq1oDgPir7UqX{xr_ z28m$7`jFO_q0dXVxd#X%tVQyan5 z=Eq!*SmF=X)%fC3bO$5qON2`75^o!!$W$H->WOX%@zXg;qBgL~2m6_ETD~Qh&g*Tz z-%lFo5wfxtRPJ06c(jhhztzo>Yn0XYddoodJtf=>*3-HPS@`bUvEKfVC3aR6vZUH) zjecJYo?wSGF+eUQkWQl~BTcM%zAjiZx87<&)TU-Uvlvs9LfYto+U9gWif!>OPvH9z*=$~IR0;($xV(yZ+syF2ahq;EMt#dH3xqVXtN z5(#ee5MS(bNdTt~mHSI1!--*Y!ckqQXsmHkenVK=*yjHI!TNc{mMI+3Iuv`{;8+nu zloz{JqqZ^(vaba+^OqnMVA&fUqSU@AxMxaOs$UVK_npB?qG8#%3-SQ16zQ|KGW@`F zqua-!Uh$d%vs!I!Qt$dT1DLa_h{ej*(A&zLh)#-?gDv(W53uGQ2|W3zlAk7%Yv}fX z(z371h{eAcyKs_vdEJ}27p+Q~MMo27+p1=rxu6q=++JnDFBq+`g6r&uKk3|78@~aQ zv;B%cJ*=;uTT%Zpx~r3iyM-G_2c7}D(ADEBsI-DkC8pknGj=f> z9Oz_G)MN}Ac&U~Q6S3JiWc_AQ9W#iDMXAm7W|C3|U9$FYW%73+SsficFC66>M24GD z`UNh%1``R(h;K0J3#Fd zRowm7f4XJGos2RJxsh$VQgj=fHS1QUn*Jy=``Mb z|Fw`k@+K|6QOLCAYNx#&3x)7XD#@$k0a-03He<`MSe~WBRsYrgY>g#URlDh)qG4hU zxk~0T%-kei4LQhY1i1j1)^UJU=V(lwP{5#gS#-@OxiY7sySb>z0V%XL>#6YNu^6Pt zUAL{txbX$a<@l8W75vM+H2mqRhaq2$5*t04v;BoFBl@qoJe-dNnz2zH;wr<3&Us_O)mZZ zm*M3~w5pi{ofzje9SQ>{YGOgEmTK5q*VD}AI6^~dZ`RQzp<)B_S!X$b(B80Up!ou~ zQ9AniXG=IWZz0{~Piil$#JncH7&vn+JMr?c44Kg%5l*Ciw2S1HZbuHxp|5UWGceq0 zKGbBzb%O4X=cL2mo~&~Y#t8q&2=&xqJ3(;MMhl=qIb{kr<@+RyAi^7`w}UE6o1Z!O z!zcB0%nc&D@LbOKLF@rHIzlT49DNw6JhV*^>|g^I{T-Id&|W@~UYho6kkaHtnJlpR zrrG;UZ&&nY5E?`x0~0!|p1=Mw{AM9_qYFv$ z+dGG1iKA|zO6x|bmGbW8Ogu#;b zaC0FnOzJzizoD##Kw5KU)*x$*g-mdFw1|;ZOvi?h{|hly)e#ku5^wxDdb^&O9yL;3 zB5{5;|AlI{(NQ00cy&~^Q=}y{HSYqqT=Q>m=cJ&`W76{ruz|n>N15PH1Badza%?@X0)%f+so?nQ0{LuHa<1_JX!?AXLrPh<&#;}-poHehIU ztB38k4wcF89c+f7sq<#yqdG?)<3cT%26s0F4s=BMqaeKC38tXdF6Dntfo01hgWctO zqQCQ;G@xr5UI*a@rN(0V1H%<=q7CwFO2bKXA_tDV;N!P(#;y*Q7zD&x0L95om;qM; zm{m9H5v+C|gZbM^4mxi(C>QlMk0pwVngqmyt%k$cl;9E&KaU+@Fa%XNr?lb8%0zWh zjN$Ft0qbQXA>hum(`S=v@sUcCM?@Ud>4`4efdR9KirvCf;*5BUaQI_6=pV#gNf zu)h_=5}p#v-OM?Le(m{f-zxg9s(O~|?RUM7`RL1U&(XhCI)lLeALVnq{Hf8vpvVcc$lq5AUe2q*sZ4S-48hozI~9T4~oqzyC*z^ zC!?qJSWswPxt+N>?fvM01z>jU3j#TTc}W6%GPz@LK~LHO$0>c;dbi%~(ZF zcW7M|ZlgRAQuDj@ilG|g9TQYNnprEenw19BQ0(-Fue7}}vHkBkKMz$&k=T|V_Vr- zvvnH#tK2lQ%x`1EQ;iE}EN?SzLo0S=#^~%)ny}vKE}*?}knYFRDD3TSWxxMgWut!^ zsW4#Tj!tW&xMZ%8>A>A@^i^l0cuYPHoNG{SRSsJ?rt?uas- z<=~Mmc9y(^WALd#)_mVz)-pz5@ty3v!tLFvo;?CqxFfjhD%_>H$FEWLy9v(4g+WMc zn=KRt`h$&QY`1aj`%^@|^lMnuvk*7wBHIj;Z^>ovPL`HFY#PGc7N8d23yJieHmJ0j@v)oTN6p7JqmNaN&_n1j6cO%ww*F`#GXjryfz00F=QRN+ z7Jf|{|AvpGUrccSV?I)Gus&5>@ND)Ar0CYflO4eu26mB>_@8C#?qQ$R1^4IIzmA)T zbB)yD3^#q3r4=~xH(gfF?S)UW^)u*N6E^%N7psXvcK9eS-f`H1V54T@O($a*=~#bZ=R_;}AwGKPi> z2>6B5yR=pn@G+_j!w*|=1;9hCB^7e2dEq2gS^~oQN0X+G0K~Qlr!C3)8Z>HiTVRJO%kTdnjacNZZ~387GkeI{ zL=t13(^DuiC9ls-rB#g!5W|QWig=5f9bV&!bzCA%okS0pg%@s#h-ke4@{vh4iS7gH zg`TkJBk^yIF-7h-d`v`Z3@iWQBc$hi1X;w1+(5NC%fETXL`X(g^1;od?V>T+UY{?5 z$pH;7`l-YpyLxXL%ifJ?=gJ_W=@2~+Ce{#R*KYUPPu8N788xtIto{Twl4xY91e@+%3nMqVkAX)eoy1m{-WIE9?I zPsxKi5Ce;eUI6r1&GrLOBYh>FKj*NW{$pgt)cMwRfav*##nVFEbM-4=VBO028E&9P6j=Wbw?Ds*1O*79+nfXbZ{{7B?#wP)*{|Tgx%0drPH*FW z>>fxR_;HHo_Tu6&!%JtpbLochVB z1rizl>-f(jwe#|OHV4FxQ8FAgeFARlrb7%zz9Fx$undkO_+q*}{y-`dxuvrb z6@bh$Nkm>FN&=gth)q@W_yd#FM9zfn7s^A`=9G1f54g6_(g9CaOFo7p)J#gxQ0x<% zZAcgb(f8N|;2zJ*-1w#> zXvf_~@Pd>2$T-`mmZJz|M|m-~HE@(E632)+Nr+`Jill+ij8;qZG)g{mA;|L`Z<3-i z$F1?AhMc9k?X?o$X%zxF@E>(c{W zfu>6Ze*ZHY_;tXH12P1`Oj6J1%;(?dYf_PFeSd9KFzw^Ygr`%sg_IazVA6eqG;`9I zG%Z|FQqbI+uFAmPb6H}j_OkS}G#%f|#NVu^LxxD26{Igz=VvR}f*5(ZqLNqk9{v>q ztp!$h=|KA1xdW7)^dVg{y$ObP#qa?MKRo;%v*ZBUz?GyBGk-z^znh$j68jGbT-Zk4?Oj@h7v9oP2~(l*sD*H z9ywNTxDuQkOZDu;un67lHHmuTja<@8e5GK8(WS(u{638#s@sqhbel%``5wQEm6s1c zfbmX!>j;@QmXqt>>`Hp(=gSG3Y-?S3Ahz>^C)KGQ*YX47JLf0q1{LGj|MHCoa0%c~ zhv}dY|INJqLj2#fE_yJ}djEhp=Js%rL<
jq7@s~2%03ImJa4OuPbS9ec^&IUEjdL-cVXS+ut1|;PQ?&FIRNzC&_ z)4uKgG>LnF`bn9SqS{4Muh>gSwq4QJ&~hABw>g7=Puf@FXaL5DaMQc(lOoRtnM@1z zn28TC3FWL$Z}2qd1njFR!ylCLPe2MC+WM*?8baSLMiC5TrD)jVF}2<$C(M)Q@{_?$ z_V6}ozJclDWHA%Z4yKq_3Ch(FTw z%LnlIAIQs4W{sKAT8|pmo?)DBLFbi~r4|S_F$Fre!CUYq6^W4BO|H{HdYt{&-0&9$X=hM(j_V&Ct zHe@+MulwHB$j?p6=&9+J@mdxR%u;yVGfFArowYoXbWk5I9>=*=F~OaWdcX3G;b?V^ zSpR3a*UYGuTTyL&9eJ)eP^P_M0m?6k=&FU%q`Lgow3EpP?D6O{4I266)7rNojt~Mn zSnoir5_X~__k4bpYmcIp)WfN#=R1^GR!D{UxXU$kfKe56>v%&=CXRmkk=a zR27x_ZI@~F2WtvvSqHzbN!TAlJu?qE(P$SOwk$ib^q0{2s|_acw$u9qearciiGeDN z=>Pcld`;NK?2j)+LGijvM6Vav*TxeLaZRxjf_JhH;j6#AJ9ygLCMC3>7-x=55{yCQn+O5YtT>QUaOZ$#+>mLwFOuUPKzT<%{;=b=z%*Us6(`v4vA?vWJ-Cv zS1ip_7|9w|%G(F6O(=w@1Nq7ml3Q63`P4-!py+MDO&@cD)Neb;+1P&Fcw+A#TA`as z={5lJX}%s~+m>iBnNR6r>fCYDMPKH@UBt9j&BN6Ld3*IM#26$E>l^oS z1cRa5+B&8McmrJ!?8B;ku)|ZB&PtI`jm5%NC9F$JbY+`{h`r$qUC7=Wh?$T4j8|U} z2a<^2=q#R| z{3J@reE4mu!>wAMI|h^A^&FUQ-AdR`k=|3x(Mww&OsIQ4-JS%Z|5Z8%Ru0Tf|b-qlg z5KlmmfJGSt0`aVxHB=_l4f0C;+J{=fZD_9+pRiPk9doh(|D3 zhyTu0hs($?6sbHdd{5$x8BSc1GA4)!IbneuDB1t^Z zuk82vH{n~YaYaE+?>x0%IvcN+Uc#$Rk0>>Z{Q8a@nP<>RMn z;S}ur#AW$$XftH&Q#8;04|AsaCr$^Jo-$wePfWas`)UP|2wHWiD(1q?s?^#lPod9` zPqtd_rm?_!_u$%khw@Lo^KiGZ|5Hv0s@PpR#Qp{4Y~ku)L0Mb*d2(sS-^l^ zezo8#=nJ4ee?K8u%J>WDxl+HC$!L+-@h#T8k3uwZ3f zvz-n`%;jxnvMXXp;K7Rd(}240&E=ipt3npQ3jw<(4Mvm88@F^+*)_RaMNcLI2%q?=QkYsX}r^l|^Jws$4hN z`$fcsK9@Hv*)EBwyC;eA8xa={x@ML27oq2R{W@Diy(IKZ{5k4Rp??qd|Ml`95c7Pz zc2UDY+3IQqgGT!m^XK*@DDcY-0u(sal?T=Z4}N8$^XvPRzgXtN9haBj7%vH`yVv+< zg23&IfRT53H|naqJWQ}wyQ~f-E;Y_ko<~!B1yo? z_+yuV$|kH!QqIIr$o?hePlyI6;LCnE6IUV!34A%Sepd@FQuOkz)m5fZ@_QlwA*;E3 z%|mp_Qg!!|C_yUpr$GhEVwZ#JOni_Eq=5gT#azB)xtjAV)W4H)Zm(o*B!ZeX0il1_X0R_opNT)C{S_L_MZ_-;4ayalbMH(Xjt(^Pe-pizC*1Ux zp1s1m68DM(F-DLQ!1(%iT_61t z_L(?2^H1#ao&|am3dSav2Tzw%uI`>A2-F`50Djm6M#km&##I?uY#^Q-sDt_+%K#Ox xC|9h5&hb+Qn7Ij<$G&{vzgq6}e}(+b{N$$q7~qKv0AK|Exq%J<)N!8w^najIUMBzm literal 0 HcmV?d00001 diff --git a/server/config/logger.ts b/server/config/logger.ts new file mode 100644 index 0000000..6d33ea8 --- /dev/null +++ b/server/config/logger.ts @@ -0,0 +1,78 @@ +import * as winston from 'winston'; +import 'winston-daily-rotate-file'; +import * as path from 'path'; + +// Define log levels +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + debug: 4, +}; + +// Define log level based on environment +const level = () => { + const env = process.env.NODE_ENV || 'development'; + return env === 'development' ? 'debug' : 'warn'; +}; + +// Define colors for each level +const colors = { + error: 'red', + warn: 'yellow', + info: 'green', + http: 'magenta', + debug: 'blue', +}; + +// Add colors to winston +winston.addColors(colors); + +// Custom format for logging +const format = winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }), + winston.format.colorize({ all: true }), + winston.format.printf( + (info) => `${info.timestamp} ${info.level}: ${info.message}` + ) +); + +// Define file transport options +const fileRotateTransport = new winston.transports.DailyRotateFile({ + filename: path.join('logs', '%DATE%-server.log'), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + format: winston.format.combine( + winston.format.uncolorize(), + winston.format.timestamp(), + winston.format.json() + ), +}); + +// Create the logger +const logger = winston.createLogger({ + level: level(), + levels, + format, + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ), + }), + fileRotateTransport, + ], +}); + +// Create a stream object for Morgan middleware +const stream = { + write: (message: string) => { + logger.http(message.trim()); + }, +}; + +export { logger, stream }; \ No newline at end of file diff --git a/server/index.js b/server/index.js index 1e51f94..6df2748 100644 --- a/server/index.js +++ b/server/index.js @@ -3,7 +3,7 @@ import cors from 'cors'; import dotenv from 'dotenv'; import { PrismaClient } from '@prisma/client'; import authRoutes from './routes/auth.js'; -//import articleRoutes from './routes/articles.js'; +import articleRoutes from './routes/articles'; import userRoutes from './routes/users.js'; dotenv.config(); @@ -16,7 +16,7 @@ app.use(express.json()); // Routes app.use('/api/auth', authRoutes); -//app.use('/api/articles', articleRoutes); +app.use('/api/articles', articleRoutes); app.use('/api/users', userRoutes); const PORT = process.env.PORT || 5000; diff --git a/server/index.ts b/server/index.ts index 454d4c0..959bd62 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,24 +1,45 @@ import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; -import authRoutes from './routes/auth.js'; -import articleRoutes from './routes/articles.js'; -import userRoutes from './routes/users.js'; +import { logger, stream } from './config/logger.js'; +import { requestLogger } from './middleware/logging/requestLogger.js'; +import { errorLogger } from './middleware/error/errorLogger.js'; +import authRoutes from './routes/auth/index.js'; +import articleRoutes from './routes/articles/index.js'; +import userRoutes from './routes/users/index.js'; +// Load environment variables dotenv.config(); const app = express(); +// Middleware app.use(cors()); app.use(express.json()); +app.use(requestLogger); // Routes app.use('/api/auth', authRoutes); app.use('/api/articles', articleRoutes); app.use('/api/users', userRoutes); +// Error handling +app.use(errorLogger); + const PORT = process.env.PORT || 5000; app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); + logger.info(`Server running on port ${PORT}`); +}); + +// Handle uncaught exceptions +process.on('uncaughtException', (error) => { + logger.error('Uncaught Exception:', error); + process.exit(1); +}); + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason, promise) => { + logger.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); }); \ No newline at end of file diff --git a/server/middleware/auth.js b/server/middleware/auth.js deleted file mode 100644 index 4d5eed2..0000000 --- a/server/middleware/auth.js +++ /dev/null @@ -1,28 +0,0 @@ -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); - -export const auth = async (req, res, next) => { - try { - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - throw new Error(); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET); - const user = await prisma.user.findUnique({ - where: { id: decoded.id } - }); - - if (!user) { - throw new Error(); - } - - req.user = user; - next(); - } catch (error) { - res.status(401).json({ error: 'Please authenticate' }); - } -}; \ No newline at end of file diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts deleted file mode 100644 index 4609647..0000000 --- a/server/middleware/auth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client'; -import { User } from '../../src/types/auth'; - -const prisma = new PrismaClient(); - -interface AuthRequest extends Request { - user?: User; -} - -export const auth = async (req: AuthRequest, res: Response, next: NextFunction) => { - try { - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - throw new Error(); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret') as { id: string }; - const user = await prisma.user.findUnique({ - where: { id: decoded.id }, - select: { - id: true, - email: true, - displayName: true, - permissions: true - } - }); - - if (!user) { - throw new Error(); - } - - req.user = user as User; - next(); - } catch (error) { - res.status(401).json({ error: 'Please authenticate' }); - } -}; \ No newline at end of file diff --git a/server/middleware/auth/auth.ts b/server/middleware/auth/auth.ts new file mode 100644 index 0000000..6de5ba2 --- /dev/null +++ b/server/middleware/auth/auth.ts @@ -0,0 +1,29 @@ +import { Response, NextFunction } from 'express'; +import { AuthRequest } from './types.js'; +import { extractToken } from './extractToken.js'; +import { validateToken } from './validateToken.js'; +import { getUser } from './getUser.js'; + +export async function auth(req: AuthRequest, res: Response, next: NextFunction) { + try { + const token = extractToken(req); + if (!token) { + return res.status(401).json({ error: 'No token provided' }); + } + + const payload = validateToken(token); + if (!payload) { + return res.status(401).json({ error: 'Invalid token' }); + } + + const user = await getUser(payload.id); + if (!user) { + return res.status(401).json({ error: 'User not found' }); + } + + req.user = user; + next(); + } catch { + res.status(401).json({ error: 'Authentication failed' }); + } +} \ No newline at end of file diff --git a/server/middleware/auth/extractToken.ts b/server/middleware/auth/extractToken.ts new file mode 100644 index 0000000..f722490 --- /dev/null +++ b/server/middleware/auth/extractToken.ts @@ -0,0 +1,11 @@ +import { Request } from 'express'; + +export function extractToken(req: Request): string | null { + const authHeader = req.header('Authorization'); + if (!authHeader) return null; + + const [bearer, token] = authHeader.split(' '); + if (bearer !== 'Bearer' || !token) return null; + + return token; +} \ No newline at end of file diff --git a/server/middleware/auth/getUser.ts b/server/middleware/auth/getUser.ts new file mode 100644 index 0000000..d2fa81a --- /dev/null +++ b/server/middleware/auth/getUser.ts @@ -0,0 +1,22 @@ +import { PrismaClient } from '@prisma/client'; +import { User } from '../../../src/types/auth.js'; + +const prisma = new PrismaClient(); + +export async function getUser(userId: string): Promise { + try { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { + id: true, + email: true, + displayName: true, + permissions: true + } + }); + + return user as User | null; + } catch { + return null; + } +} \ No newline at end of file diff --git a/server/middleware/auth/index.ts b/server/middleware/auth/index.ts new file mode 100644 index 0000000..c775c10 --- /dev/null +++ b/server/middleware/auth/index.ts @@ -0,0 +1,5 @@ +export { auth } from './auth.js'; +export { extractToken } from './extractToken.js'; +export { validateToken } from './validateToken.js'; +export { getUser } from './getUser.js'; +export * from './types.js'; \ No newline at end of file diff --git a/server/middleware/auth/types.ts b/server/middleware/auth/types.ts new file mode 100644 index 0000000..c154991 --- /dev/null +++ b/server/middleware/auth/types.ts @@ -0,0 +1,12 @@ +import { Request } from 'express'; +import { User } from '../../../src/types/auth.js'; + +export interface AuthRequest extends Request { + user?: User; +} + +export interface JwtPayload { + id: string; + iat?: number; + exp?: number; +} \ No newline at end of file diff --git a/server/middleware/auth/validateToken.ts b/server/middleware/auth/validateToken.ts new file mode 100644 index 0000000..0d56d42 --- /dev/null +++ b/server/middleware/auth/validateToken.ts @@ -0,0 +1,10 @@ +import jwt from 'jsonwebtoken'; +import { JwtPayload } from './types.js'; + +export function validateToken(token: string): JwtPayload | null { + try { + return jwt.verify(token, process.env.JWT_SECRET || '') as JwtPayload; + } catch { + return null; + } +} \ No newline at end of file diff --git a/server/middleware/error/errorHandler.ts b/server/middleware/error/errorHandler.ts new file mode 100644 index 0000000..26ad5c3 --- /dev/null +++ b/server/middleware/error/errorHandler.ts @@ -0,0 +1,20 @@ +import { Request, Response, NextFunction } from 'express'; + +export interface AppError extends Error { + statusCode?: number; +} + +export function errorHandler( + err: AppError, + req: Request, + res: Response, + next: NextFunction +) { + const statusCode = err.statusCode || 500; + const message = err.message || 'Internal Server Error'; + + res.status(statusCode).json({ + error: message, + stack: process.env.NODE_ENV === 'development' ? err.stack : undefined + }); +} \ No newline at end of file diff --git a/server/middleware/error/errorLogger.ts b/server/middleware/error/errorLogger.ts new file mode 100644 index 0000000..b425a09 --- /dev/null +++ b/server/middleware/error/errorLogger.ts @@ -0,0 +1,27 @@ +import { Request, Response, NextFunction } from 'express'; +import { logger } from '../../config/logger.js'; + +export interface AppError extends Error { + statusCode?: number; + details?: never; +} + +export const errorLogger = ( + err: AppError, + req: Request, + res: Response, + next: NextFunction +) => { + const errorDetails = { + message: err.message, + stack: err.stack, + timestamp: new Date().toISOString(), + path: req.path, + method: req.method, + statusCode: err.statusCode || 500, + details: err.details, + }; + + logger.error('Application error:', errorDetails); + next(err); +}; \ No newline at end of file diff --git a/server/middleware/logging/requestLogger.ts b/server/middleware/logging/requestLogger.ts new file mode 100644 index 0000000..dc81f37 --- /dev/null +++ b/server/middleware/logging/requestLogger.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from 'express'; +import { logger } from '../../config/logger.js'; + +export const requestLogger = (req: Request, res: Response, next: NextFunction) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + const message = `${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`; + + if (res.statusCode >= 500) { + logger.error(message); + } else if (res.statusCode >= 400) { + logger.warn(message); + } else { + logger.info(message); + } + }); + + next(); +}; \ No newline at end of file diff --git a/server/middleware/validation/validateRequest.ts b/server/middleware/validation/validateRequest.ts new file mode 100644 index 0000000..beffd2a --- /dev/null +++ b/server/middleware/validation/validateRequest.ts @@ -0,0 +1,17 @@ +import { Request, Response, NextFunction } from 'express'; +import { Schema } from 'zod'; + +export function validateRequest(schema: Schema) { + return async (req: Request, res: Response, next: NextFunction) => { + try { + await schema.parseAsync({ + body: req.body, + query: req.query, + params: req.params + }); + next(); + } catch (error) { + res.status(400).json({ error: 'Invalid request data' }); + } + }; +} \ No newline at end of file diff --git a/server/routes/articles.js b/server/routes/articles.js deleted file mode 100644 index b582e23..0000000 --- a/server/routes/articles.js +++ /dev/null @@ -1,91 +0,0 @@ -import express from 'express'; -import { PrismaClient } from '@prisma/client'; -import { auth } from '../middleware/auth.js'; - -const router = express.Router(); -const prisma = new PrismaClient(); - -// Search articles -router.get('/search', async (req, res) => { - try { - const { q, page = 1, limit = 9 } = req.query; - const skip = (page - 1) * limit; - - const where = { - OR: [ - { title: { contains: q, mode: 'insensitive' } }, - { excerpt: { contains: q, mode: 'insensitive' } }, - { content: { contains: q, mode: 'insensitive' } }, - ] - }; - - const [articles, total] = await Promise.all([ - prisma.article.findMany({ - where, - include: { - author: { - select: { - id: true, - displayName: true, - email: true - } - } - }, - skip, - take: parseInt(limit), - orderBy: { publishedAt: 'desc' } - }), - prisma.article.count({ where }) - ]); - - res.json({ - articles, - totalPages: Math.ceil(total / limit), - currentPage: parseInt(page) - }); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -// Get articles with pagination and filters -router.get('/', async (req, res) => { - try { - const { page = 1, category, city } = req.query; - const perPage = 6; - - const where = { - ...(category && { category }), - ...(city && { city }) - }; - - const [articles, total] = await Promise.all([ - prisma.article.findMany({ - where, - include: { - author: { - select: { - id: true, - displayName: true, - email: true - } - } - }, - skip: (page - 1) * perPage, - take: perPage, - orderBy: { publishedAt: 'desc' } - }), - prisma.article.count({ where }) - ]); - - res.json({ - articles, - totalPages: Math.ceil(total / perPage), - currentPage: parseInt(page) - }); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -// Rest of the routes remain the same... \ No newline at end of file diff --git a/server/routes/articles/controllers/crud.ts b/server/routes/articles/controllers/crud.ts new file mode 100644 index 0000000..d2a570c --- /dev/null +++ b/server/routes/articles/controllers/crud.ts @@ -0,0 +1,123 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../../src/lib/prisma'; +import { AuthRequest } from '../../../middleware/auth'; +import { checkPermission } from '../../../utils/permissions.js'; + +export async function getArticle(req: Request, res: Response) { + try { + const article = await prisma.article.findUnique({ + where: { id: req.params.id }, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + if (!article) { + return res.status(404).json({ error: 'Article not found' }); + } + + res.json(article); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function createArticle(req: AuthRequest, res: Response) { + try { + const { title, excerpt, content, category, city, coverImage, readTime } = req.body; + + if (!req.user || !checkPermission(req.user, category, 'create')) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const article = await prisma.article.create({ + data: { + title, + excerpt, + content, + category, + city, + coverImage, + readTime, + authorId: req.user.id + }, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + res.status(201).json(article); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function updateArticle(req: AuthRequest, res: Response) { + try { + const article = await prisma.article.findUnique({ + where: { id: req.params.id } + }); + + if (!article) { + return res.status(404).json({ error: 'Article not found' }); + } + + if (!req.user || !checkPermission(req.user, article.category, 'edit')) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const updatedArticle = await prisma.article.update({ + where: { id: req.params.id }, + data: req.body, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + res.json(updatedArticle); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function deleteArticle(req: AuthRequest, res: Response) { + try { + const article = await prisma.article.findUnique({ + where: { id: req.params.id } + }); + + if (!article) { + return res.status(404).json({ error: 'Article not found' }); + } + + if (!req.user || !checkPermission(req.user, article.category, 'delete')) { + return res.status(403).json({ error: 'Permission denied' }); + } + + await prisma.article.delete({ + where: { id: req.params.id } + }); + + res.json({ message: 'Article deleted successfully' }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/articles/controllers/list.ts b/server/routes/articles/controllers/list.ts new file mode 100644 index 0000000..4df7e31 --- /dev/null +++ b/server/routes/articles/controllers/list.ts @@ -0,0 +1,41 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../../src/lib/prisma'; + +export async function listArticles(req: Request, res: Response) { + try { + const { page = 1, category, city } = req.query; + const perPage = 6; + + const where = { + ...(category && { category: category as string }), + ...(city && { city: city as string }) + }; + + const [articles, total] = await Promise.all([ + prisma.article.findMany({ + where, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + }, + skip: ((page as number) - 1) * perPage, + take: perPage, + orderBy: { publishedAt: 'desc' } + }), + prisma.article.count({ where }) + ]); + + res.json({ + articles, + totalPages: Math.ceil(total / perPage), + currentPage: parseInt(page as string) + }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/articles/controllers/search.ts b/server/routes/articles/controllers/search.ts new file mode 100644 index 0000000..ca9ed1b --- /dev/null +++ b/server/routes/articles/controllers/search.ts @@ -0,0 +1,44 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../../src/lib/prisma'; + +export async function searchArticles(req: Request, res: Response) { + try { + const { q, page = 1, limit = 9 } = req.query; + const skip = ((page as number) - 1) * (limit as number); + + const where = { + OR: [ + { title: { contains: q as string, mode: 'insensitive' } }, + { excerpt: { contains: q as string, mode: 'insensitive' } }, + { content: { contains: q as string, mode: 'insensitive' } }, + ] + }; + + const [articles, total] = await Promise.all([ + prisma.article.findMany({ + where, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + }, + skip, + take: parseInt(limit as string), + orderBy: { publishedAt: 'desc' } + }), + prisma.article.count({ where }) + ]); + + res.json({ + articles, + totalPages: Math.ceil(total / (limit as number)), + currentPage: parseInt(page as string) + }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/articles/crud.ts b/server/routes/articles/crud.ts new file mode 100644 index 0000000..9b08d19 --- /dev/null +++ b/server/routes/articles/crud.ts @@ -0,0 +1,93 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../src/lib/prisma'; + +export async function getArticle(req: Request, res: Response) { + try { + const article = await prisma.article.findUnique({ + where: { id: req.params.id }, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + if (!article) { + return res.status(404).json({ error: 'Article not found' }); + } + + res.json(article); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function createArticle(req: Request, res: Response) { + try { + const { title, excerpt, content, category, city, coverImage, readTime } = req.body; + + const article = await prisma.article.create({ + data: { + title, + excerpt, + content, + category, + city, + coverImage, + readTime, + authorId: req.user!.id + }, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + res.status(201).json(article); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function updateArticle(req: Request, res: Response) { + try { + const article = await prisma.article.update({ + where: { id: req.params.id }, + data: req.body, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + } + }); + + res.json(article); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function deleteArticle(req: Request, res: Response) { + try { + await prisma.article.delete({ + where: { id: req.params.id } + }); + + res.json({ message: 'Article deleted successfully' }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/articles/index.ts b/server/routes/articles/index.ts new file mode 100644 index 0000000..2b5bd41 --- /dev/null +++ b/server/routes/articles/index.ts @@ -0,0 +1,19 @@ +import express from 'express'; +import { auth } from '../../middleware/auth'; +import { searchArticles } from './controllers/search.js'; +import { listArticles } from './controllers/list.js'; +import { getArticle, createArticle, updateArticle, deleteArticle } from './controllers/crud.js'; + +const router = express.Router(); + +// Search and list routes +router.get('/search', searchArticles); +router.get('/', listArticles); + +// CRUD routes +router.get('/:id', getArticle); +router.post('/', auth, createArticle); +router.put('/:id', auth, updateArticle); +router.delete('/:id', auth, deleteArticle); + +export default router; \ No newline at end of file diff --git a/server/routes/articles/list.ts b/server/routes/articles/list.ts new file mode 100644 index 0000000..578fa6d --- /dev/null +++ b/server/routes/articles/list.ts @@ -0,0 +1,41 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../src/lib/prisma'; + +export async function listArticles(req: Request, res: Response) { + try { + const { page = 1, category, city } = req.query; + const perPage = 6; + + const where = { + ...(category && { category: category as string }), + ...(city && { city: city as string }) + }; + + const [articles, total] = await Promise.all([ + prisma.article.findMany({ + where, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + }, + skip: ((page as number) - 1) * perPage, + take: perPage, + orderBy: { publishedAt: 'desc' } + }), + prisma.article.count({ where }) + ]); + + res.json({ + articles, + totalPages: Math.ceil(total / perPage), + currentPage: parseInt(page as string) + }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/articles/search.ts b/server/routes/articles/search.ts new file mode 100644 index 0000000..31efe06 --- /dev/null +++ b/server/routes/articles/search.ts @@ -0,0 +1,44 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../../src/lib/prisma'; + +export async function searchArticles(req: Request, res: Response) { + try { + const { q, page = 1, limit = 9 } = req.query; + const skip = ((page as number) - 1) * (limit as number); + + const where = { + OR: [ + { title: { contains: q as string, mode: 'insensitive' } }, + { excerpt: { contains: q as string, mode: 'insensitive' } }, + { content: { contains: q as string, mode: 'insensitive' } }, + ] + }; + + const [articles, total] = await Promise.all([ + prisma.article.findMany({ + where, + include: { + author: { + select: { + id: true, + displayName: true, + email: true + } + } + }, + skip, + take: parseInt(limit as string), + orderBy: { publishedAt: 'desc' } + }), + prisma.article.count({ where }) + ]); + + res.json({ + articles, + totalPages: Math.ceil(total / (limit as number)), + currentPage: parseInt(page as string) + }); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/auth.js b/server/routes/auth.js deleted file mode 100644 index 14db9d8..0000000 --- a/server/routes/auth.js +++ /dev/null @@ -1,38 +0,0 @@ -import express from 'express'; -import bcrypt from 'bcryptjs'; -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client'; - -const router = express.Router(); -const prisma = new PrismaClient(); - -// Login -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await prisma.user.findUnique({ - where: { email } - }); - - if (!user || !await bcrypt.compare(password, user.password)) { - return res.status(401).json({ error: 'Invalid credentials' }); - } - - const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET); - - res.json({ - token, - user: { - id: user.id, - email: user.email, - displayName: user.displayName, - permissions: user.permissions - } - }); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -export default router; \ No newline at end of file diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 3883f76..8bf9fff 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -1,6 +1,6 @@ import express from 'express'; import { authService } from '../services/authService.js'; -import { auth } from '../middleware/auth.js'; +import { auth } from '../middleware/auth'; const router = express.Router(); @@ -10,7 +10,7 @@ router.post('/login', async (req, res) => { const { email, password } = req.body; const { user, token } = await authService.login(email, password); res.json({ user, token }); - } catch (error) { + } catch { res.status(401).json({ error: 'Invalid credentials' }); } }); @@ -19,7 +19,7 @@ router.post('/login', async (req, res) => { router.get('/me', auth, async (req, res) => { try { res.json(req.user); - } catch (error) { + } catch { res.status(500).json({ error: 'Server error' }); } }); diff --git a/server/routes/auth/controllers/auth.ts b/server/routes/auth/controllers/auth.ts new file mode 100644 index 0000000..5addcc0 --- /dev/null +++ b/server/routes/auth/controllers/auth.ts @@ -0,0 +1,36 @@ +import { Request, Response } from 'express'; +import { AuthRequest } from '../../../middleware/auth'; +import { authService } from '../../../services/authService.js'; + +export async function login(req: Request, res: Response) { + try { + const { email, password } = req.body; + const { user, token } = await authService.login(email, password); + res.json({ user, token }); + } catch { + res.status(401).json({ error: 'Invalid credentials' }); + } +} + +export async function getCurrentUser(req: AuthRequest, res: Response) { + try { + if (!req.user) { + return res.status(401).json({ error: 'Not authenticated' }); + } + res.json(req.user); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function refreshToken(req: AuthRequest, res: Response) { + try { + if (!req.user) { + return res.status(401).json({ error: 'Not authenticated' }); + } + const token = await authService.generateToken(req.user.id); + res.json({ token }); + } catch { + res.status(500).json({ error: 'Failed to refresh token' }); + } +} \ No newline at end of file diff --git a/server/routes/auth/index.ts b/server/routes/auth/index.ts new file mode 100644 index 0000000..3db9e22 --- /dev/null +++ b/server/routes/auth/index.ts @@ -0,0 +1,12 @@ +import express from 'express'; +import { auth } from '../../middleware/auth'; +import { login, getCurrentUser } from './controllers/auth.js'; +import { validateRequest } from '../../middleware/validation/validateRequest.js'; +import { loginSchema } from './validation/authSchemas.js'; + +const router = express.Router(); + +router.post('/login', validateRequest(loginSchema), login); +router.get('/me', auth, getCurrentUser); + +export default router; \ No newline at end of file diff --git a/server/routes/auth/validation/authSchemas.ts b/server/routes/auth/validation/authSchemas.ts new file mode 100644 index 0000000..40bab48 --- /dev/null +++ b/server/routes/auth/validation/authSchemas.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const loginSchema = z.object({ + body: z.object({ + email: z.string().email(), + password: z.string().min(6) + }) +}); \ No newline at end of file diff --git a/server/routes/gallery/controllers/crud.ts b/server/routes/gallery/controllers/crud.ts new file mode 100644 index 0000000..bb7384a --- /dev/null +++ b/server/routes/gallery/controllers/crud.ts @@ -0,0 +1,81 @@ +import { Request, Response } from 'express'; +import { AuthRequest } from '../../../middleware/auth/types.js'; +import { galleryService } from '../../../services/galleryService.js'; +import { s3Service } from '../../../services/s3Service.js'; +import { logger } from '../../../config/logger.js'; + +export async function createGalleryImage(req: AuthRequest, res: Response) { + try { + const { articleId } = req.params; + const { url, caption, alt, width, height, size, format } = req.body; + + const image = await galleryService.createImage({ + url, + caption, + alt, + width, + height, + size, + format, + articleId + }); + + res.status(201).json(image); + } catch (error) { + logger.error('Error creating gallery image:', error); + res.status(500).json({ error: 'Failed to create gallery image' }); + } +} + +export async function updateGalleryImage(req: AuthRequest, res: Response) { + try { + const { id } = req.params; + const { caption, alt, order } = req.body; + + const image = await galleryService.updateImage(id, { + caption, + alt, + order + }); + + res.json(image); + } catch (error) { + logger.error('Error updating gallery image:', error); + res.status(500).json({ error: 'Failed to update gallery image' }); + } +} + +export async function deleteGalleryImage(req: AuthRequest, res: Response) { + try { + const { id } = req.params; + await galleryService.deleteImage(id); + res.json({ message: 'Gallery image deleted successfully' }); + } catch (error) { + logger.error('Error deleting gallery image:', error); + res.status(500).json({ error: 'Failed to delete gallery image' }); + } +} + +export async function reorderGalleryImages(req: AuthRequest, res: Response) { + try { + const { articleId } = req.params; + const { imageIds } = req.body; + + await galleryService.reorderImages(articleId, imageIds); + res.json({ message: 'Gallery images reordered successfully' }); + } catch (error) { + logger.error('Error reordering gallery images:', error); + res.status(500).json({ error: 'Failed to reorder gallery images' }); + } +} + +export async function getArticleGallery(req: Request, res: Response) { + try { + const { articleId } = req.params; + const images = await galleryService.getArticleGallery(articleId); + res.json(images); + } catch (error) { + logger.error('Error fetching article gallery:', error); + res.status(500).json({ error: 'Failed to fetch gallery images' }); + } +} \ No newline at end of file diff --git a/server/routes/gallery/index.ts b/server/routes/gallery/index.ts new file mode 100644 index 0000000..afbe3ed --- /dev/null +++ b/server/routes/gallery/index.ts @@ -0,0 +1,19 @@ +import express from 'express'; +import { auth } from '../../middleware/auth'; +import { + createGalleryImage, + updateGalleryImage, + deleteGalleryImage, + reorderGalleryImages, + getArticleGallery +} from './controllers/crud.js'; + +const router = express.Router(); + +router.get('/article/:articleId', getArticleGallery); +router.post('/article/:articleId', auth, createGalleryImage); +router.put('/:id', auth, updateGalleryImage); +router.delete('/:id', auth, deleteGalleryImage); +router.post('/article/:articleId/reorder', auth, reorderGalleryImages); + +export default router; \ No newline at end of file diff --git a/server/routes/images/index.ts b/server/routes/images/index.ts new file mode 100644 index 0000000..cfc7668 --- /dev/null +++ b/server/routes/images/index.ts @@ -0,0 +1,58 @@ +import express from 'express'; +import multer from 'multer'; +import { auth } from '../../middleware/auth'; +import { s3Service } from '../../services/s3Service.js'; +import { logger } from '../../config/logger.js'; +import { imageResolutions } from '../../../src/config/imageResolutions.js'; + +const router = express.Router(); +const upload = multer(); + +router.post('/upload-url', auth, async (req, res) => { + try { + const { fileName, fileType, resolution } = req.body; + + const selectedResolution = imageResolutions.find(r => r.id === resolution); + if (!selectedResolution) { + throw new Error('Invalid resolution'); + } + + const { uploadUrl, imageId, key } = await s3Service.getUploadUrl(fileName, fileType); + + logger.info(`Generated upload URL for image: ${fileName}`); + res.json({ uploadUrl, imageId, key }); + } catch (error) { + logger.error('Error generating upload URL:', error); + res.status(500).json({ error: 'Failed to generate upload URL' }); + } +}); + +router.post('/process', auth, upload.single('image'), async (req, res) => { + try { + const { file } = req; + const { resolution } = req.body; + + if (!file) { + throw new Error('No file uploaded'); + } + + const selectedResolution = imageResolutions.find(r => r.id === resolution); + if (!selectedResolution) { + throw new Error('Invalid resolution'); + } + + const result = await s3Service.optimizeAndUpload( + file.buffer, + file.originalname, + selectedResolution + ); + + logger.info(`Successfully processed image: ${file.originalname}`); + res.json(result); + } catch (error) { + logger.error('Error processing image:', error); + res.status(500).json({ error: 'Failed to process image' }); + } +}); + +export default router; \ No newline at end of file diff --git a/server/routes/users.js b/server/routes/users.js deleted file mode 100644 index 082140c..0000000 --- a/server/routes/users.js +++ /dev/null @@ -1,93 +0,0 @@ -import express from 'express'; -import bcrypt from 'bcryptjs'; -import { PrismaClient } from '@prisma/client'; -import { auth } from '../middleware/auth.js'; - -const router = express.Router(); -const prisma = new PrismaClient(); - -// Get all users (admin only) -router.get('/', auth, async (req, res) => { - try { - if (!req.user.isAdmin) { - return res.status(403).json({ error: 'Admin access required' }); - } - - const users = await prisma.user.findMany({ - select: { - id: true, - email: true, - displayName: true, - permissions: true, - isAdmin: true - } - }); - - res.json(users); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -// Update user permissions (admin only) -router.put('/:id/permissions', auth, async (req, res) => { - try { - if (!req.user.isAdmin) { - return res.status(403).json({ error: 'Admin access required' }); - } - - const { id } = req.params; - const { permissions } = req.body; - - const user = await prisma.user.update({ - where: { id }, - data: { permissions }, - select: { - id: true, - email: true, - displayName: true, - permissions: true, - isAdmin: true - } - }); - - res.json(user); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -// Create new user (admin only) -router.post('/', auth, async (req, res) => { - try { - if (!req.user.isAdmin) { - return res.status(403).json({ error: 'Admin access required' }); - } - - const { email, password, displayName, permissions } = req.body; - - const hashedPassword = await bcrypt.hash(password, 10); - - const user = await prisma.user.create({ - data: { - email, - password: hashedPassword, - displayName, - permissions - }, - select: { - id: true, - email: true, - displayName: true, - permissions: true, - isAdmin: true - } - }); - - res.status(201).json(user); - } catch (error) { - res.status(500).json({ error: 'Server error' }); - } -}); - -export default router; \ No newline at end of file diff --git a/server/routes/users.ts b/server/routes/users.ts index cef7148..8a40953 100644 --- a/server/routes/users.ts +++ b/server/routes/users.ts @@ -1,12 +1,14 @@ import express from 'express'; import { userService } from '../services/userService.js'; -import { auth } from '../middleware/auth.js'; +import { auth } from '../middleware/auth'; +import { Request, Response } from 'express'; +import { User } from '../../src/types/auth.ts'; const router = express.Router(); -router.get('/', auth, async (req, res) => { +router.get('/', auth, async (req: Request, res: Response) => { try { - if (!req.user.permissions.isAdmin) { + if (!req.user?.permissions.isAdmin) { return res.status(403).json({ error: 'Admin access required' }); } const users = await userService.getUsers(); @@ -16,9 +18,9 @@ router.get('/', auth, async (req, res) => { } }); -router.put('/:id/permissions', auth, async (req, res) => { +router.put('/:id/permissions', auth, async (req: Request, res: Response) => { try { - if (!req.user.permissions.isAdmin) { + if (!req.user?.permissions.isAdmin) { return res.status(403).json({ error: 'Admin access required' }); } const { id } = req.params; diff --git a/server/routes/users/controllers/users.ts b/server/routes/users/controllers/users.ts new file mode 100644 index 0000000..1bea794 --- /dev/null +++ b/server/routes/users/controllers/users.ts @@ -0,0 +1,29 @@ +import { Response } from 'express'; +import { AuthRequest } from '../../../middleware/auth'; +import { userService } from '../../../services/userService.js'; + +export async function getUsers(req: AuthRequest, res: Response) { + try { + if (!req.user?.permissions.isAdmin) { + return res.status(403).json({ error: 'Admin access required' }); + } + const users = await userService.getUsers(); + res.json(users); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} + +export async function updateUserPermissions(req: AuthRequest, res: Response) { + try { + if (!req.user?.permissions.isAdmin) { + return res.status(403).json({ error: 'Admin access required' }); + } + const { id } = req.params; + const { permissions } = req.body; + const user = await userService.updateUserPermissions(id, permissions); + res.json(user); + } catch { + res.status(500).json({ error: 'Server error' }); + } +} \ No newline at end of file diff --git a/server/routes/users/index.ts b/server/routes/users/index.ts new file mode 100644 index 0000000..2d8be10 --- /dev/null +++ b/server/routes/users/index.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import { auth } from '../../middleware/auth'; +import { getUsers, updateUserPermissions } from './controllers/users.js'; + +const router = express.Router(); + +router.get('/', auth, getUsers); +router.put('/:id/permissions', auth, updateUserPermissions); + +export default router; \ No newline at end of file diff --git a/server/services/authService.ts b/server/services/authService.ts index 4d1ba46..6a2f270 100644 --- a/server/services/authService.ts +++ b/server/services/authService.ts @@ -1,38 +1,65 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from '@prisma/client'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; -import { User } from '../../src/types/auth'; +import { User } from '../../src/types/auth.js'; +import { logger } from '../config/logger.js'; const prisma = new PrismaClient(); export const authService = { login: async (email: string, password: string) => { - const user = await prisma.user.findUnique({ - where: { email }, - select: { - id: true, - email: true, - password: true, - displayName: true, - permissions: true + try { + logger.info(`Login attempt for user: ${email}`); + + const user = await prisma.user.findUnique({ + where: { email }, + select: { + id: true, + email: true, + password: true, + displayName: true, + permissions: true + } + }); + + if (!user) { + logger.warn(`Login failed: User not found - ${email}`); + throw new Error('Invalid credentials'); } - }); - if (!user || !await bcrypt.compare(password, user.password)) { - throw new Error('Invalid credentials'); + const isValidPassword = await bcrypt.compare(password, user.password); + if (!isValidPassword) { + logger.warn(`Login failed: Invalid password for user - ${email}`); + throw new Error('Invalid credentials'); + } + + const token = await authService.generateToken(user.id); + const { password: _, ...userWithoutPassword } = user; + + logger.info(`User logged in successfully: ${email}`); + return { + user: userWithoutPassword as User, + token + }; + } catch (error) { + logger.error('Login error:', error); + throw error; } + }, - const token = jwt.sign( - { id: user.id }, - process.env.JWT_SECRET || 'fallback-secret', - { expiresIn: '24h' } - ); - - const { password: _, ...userWithoutPassword } = user; - return { - user: userWithoutPassword as User, - token - }; + generateToken: async (userId: string) => { + try { + const token = jwt.sign( + { id: userId }, + process.env.JWT_SECRET || '', + { expiresIn: '24h' } + ); + logger.debug(`Generated token for user: ${userId}`); + return token; + } catch (error) { + logger.error('Token generation error:', error); + throw error; + } }, createUser: async (userData: { @@ -41,21 +68,28 @@ export const authService = { displayName: string; permissions: any; }) => { - const hashedPassword = await bcrypt.hash(userData.password, 10); - - const user = await prisma.user.create({ - data: { - ...userData, - password: hashedPassword - }, - select: { - id: true, - email: true, - displayName: true, - permissions: true - } - }); + try { + logger.info(`Creating new user: ${userData.email}`); + + const hashedPassword = await bcrypt.hash(userData.password, 10); + const user = await prisma.user.create({ + data: { + ...userData, + password: hashedPassword + }, + select: { + id: true, + email: true, + displayName: true, + permissions: true + } + }); - return user as User; + logger.info(`User created successfully: ${userData.email}`); + return user as User; + } catch (error) { + logger.error('User creation error:', error); + throw error; + } } -}; +}; \ No newline at end of file diff --git a/server/services/galleryService.ts b/server/services/galleryService.ts new file mode 100644 index 0000000..f6779a1 --- /dev/null +++ b/server/services/galleryService.ts @@ -0,0 +1,92 @@ +import { PrismaClient } from '@prisma/client'; +import { logger } from '../config/logger.js'; + +const prisma = new PrismaClient(); + +export const galleryService = { + createImage: async (data: { + url: string; + caption: string; + alt: string; + width: number; + height: number; + size: number; + format: string; + articleId: string; + order?: number; + }) => { + try { + const image = await prisma.galleryImage.create({ + data + }); + logger.info(`Created gallery image: ${image.id}`); + return image; + } catch (error) { + logger.error('Error creating gallery image:', error); + throw error; + } + }, + + updateImage: async ( + id: string, + data: { + caption?: string; + alt?: string; + order?: number; + } + ) => { + try { + const image = await prisma.galleryImage.update({ + where: { id }, + data + }); + logger.info(`Updated gallery image: ${id}`); + return image; + } catch (error) { + logger.error(`Error updating gallery image ${id}:`, error); + throw error; + } + }, + + deleteImage: async (id: string) => { + try { + await prisma.galleryImage.delete({ + where: { id } + }); + logger.info(`Deleted gallery image: ${id}`); + } catch (error) { + logger.error(`Error deleting gallery image ${id}:`, error); + throw error; + } + }, + + reorderImages: async (articleId: string, imageIds: string[]) => { + try { + await prisma.$transaction( + imageIds.map((id, index) => + prisma.galleryImage.update({ + where: { id }, + data: { order: index } + }) + ) + ); + logger.info(`Reordered gallery images for article: ${articleId}`); + } catch (error) { + logger.error(`Error reordering gallery images for article ${articleId}:`, error); + throw error; + } + }, + + getArticleGallery: async (articleId: string) => { + try { + const images = await prisma.galleryImage.findMany({ + where: { articleId }, + orderBy: { order: 'asc' } + }); + return images; + } catch (error) { + logger.error(`Error fetching gallery for article ${articleId}:`, error); + throw error; + } + } +}; \ No newline at end of file diff --git a/server/services/s3Service.ts b/server/services/s3Service.ts new file mode 100644 index 0000000..076a7b9 --- /dev/null +++ b/server/services/s3Service.ts @@ -0,0 +1,81 @@ +import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { v4 as uuidv4 } from 'uuid'; +import sharp from 'sharp'; +import { logger } from '../config/logger.js'; + +const s3Client = new S3Client({ + region: process.env.AWS_REGION || 'us-east-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' + } +}); + +const BUCKET_NAME = process.env.AWS_S3_BUCKET || ''; + +export const s3Service = { + getUploadUrl: async (fileName: string, fileType: string) => { + const imageId = uuidv4(); + const key = `uploads/${imageId}-${fileName}`; + + const command = new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + ContentType: fileType + }); + + try { + const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); + logger.info(`Generated pre-signed URL for upload: ${key}`); + return { uploadUrl, imageId, key }; + } catch (error) { + logger.error('Error generating pre-signed URL:', error); + throw error; + } + }, + + optimizeAndUpload: async (buffer: Buffer, key: string, resolution: { width: number; height: number }) => { + try { + let sharpInstance = sharp(buffer); + + // Get image metadata + const metadata = await sharpInstance.metadata(); + + // Resize if resolution is specified + if (resolution.width > 0 && resolution.height > 0) { + sharpInstance = sharpInstance.resize(resolution.width, resolution.height, { + fit: 'inside', + withoutEnlargement: true + }); + } + + // Convert to WebP for better compression + const optimizedBuffer = await sharpInstance + .webp({ quality: 80 }) + .toBuffer(); + + // Upload optimized image + const optimizedKey = key.replace(/\.[^/.]+$/, '.webp'); + await s3Client.send(new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: optimizedKey, + Body: optimizedBuffer, + ContentType: 'image/webp' + })); + + logger.info(`Successfully optimized and uploaded image: ${optimizedKey}`); + + return { + key: optimizedKey, + width: metadata.width, + height: metadata.height, + format: 'webp', + size: optimizedBuffer.length + }; + } catch (error) { + logger.error('Error optimizing and uploading image:', error); + throw error; + } + } +}; \ No newline at end of file diff --git a/server/utils/permissions.ts b/server/utils/permissions.ts new file mode 100644 index 0000000..1241b14 --- /dev/null +++ b/server/utils/permissions.ts @@ -0,0 +1,16 @@ +import { Category, City } from '../../src/types'; +import { User } from '../../src/types/auth'; + +export const checkPermission = ( + user: User, + category: Category, + action: 'create' | 'edit' | 'delete' +): boolean => { + if (user.permissions.isAdmin) return true; + return !!user.permissions.categories[category]?.[action]; +}; + +export const checkCityAccess = (user: User, city: City): boolean => { + if (user.permissions.isAdmin) return true; + return user.permissions.cities.includes(city); +}; \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 04326bd..f53d085 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import axios from 'axios'; import { useAuthStore } from './stores/authStore'; @@ -8,6 +8,7 @@ import { AdminPage } from './pages/AdminPage'; import { LoginPage } from './pages/LoginPage'; import { UserManagementPage } from './pages/UserManagementPage'; import { SearchPage } from './pages/SearchPage'; +import { BookmarksPage } from './pages/BookmarksPage'; import { Footer } from './components/Footer'; import { AuthGuard } from './components/AuthGuard'; @@ -44,6 +45,7 @@ function App() { } /> } /> } /> + } /> } /> { + if (bookmarked) { + removeBookmark(article.id); + } else { + addBookmark(article); + } + }; + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/FeaturedSection.tsx b/src/components/FeaturedSection.tsx index 5e5151e..9d933f0 100644 --- a/src/components/FeaturedSection.tsx +++ b/src/components/FeaturedSection.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import { useState, useMemo } from 'react'; import { useLocation, useSearchParams } from 'react-router-dom'; import { ArticleCard } from './ArticleCard'; import { Pagination } from './Pagination'; diff --git a/src/components/Footer/DesignStudioLogo.tsx b/src/components/Footer/DesignStudioLogo.tsx new file mode 100644 index 0000000..ac855af --- /dev/null +++ b/src/components/Footer/DesignStudioLogo.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Palette } from 'lucide-react'; + +export function DesignStudioLogo() { + return ( + + + Designed by StackBlitz Studio + + ); +} \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer/index.tsx similarity index 93% rename from src/components/Footer.tsx rename to src/components/Footer/index.tsx index 47abada..53408d0 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Mail, Phone, Instagram, Twitter, Facebook, ExternalLink } from 'lucide-react'; +import { DesignStudioLogo } from './DesignStudioLogo'; export function Footer() { return ( @@ -119,9 +120,13 @@ export function Footer() {
-

- © {new Date().getFullYear()} CultureScope. All rights reserved. -

+
+

+ © {new Date().getFullYear()} CultureScope. All rights reserved. +

+ + +
Privacy Policy diff --git a/src/components/GalleryManager.tsx b/src/components/GalleryManager.tsx index 7ccf311..1eeed5b 100644 --- a/src/components/GalleryManager.tsx +++ b/src/components/GalleryManager.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { X, Plus, Move, Pencil, Trash2 } from 'lucide-react'; import { GalleryImage } from '../types'; diff --git a/src/components/GalleryManager/GalleryGrid.tsx b/src/components/GalleryManager/GalleryGrid.tsx index 5dc451a..275d80a 100644 --- a/src/components/GalleryManager/GalleryGrid.tsx +++ b/src/components/GalleryManager/GalleryGrid.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Move, Pencil, Trash2 } from 'lucide-react'; import { GalleryImage } from '../../types'; diff --git a/src/components/GalleryManager/ImageForm.tsx b/src/components/GalleryManager/ImageForm.tsx index 300c61c..28b2a03 100644 --- a/src/components/GalleryManager/ImageForm.tsx +++ b/src/components/GalleryManager/ImageForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Plus } from 'lucide-react'; interface ImageFormProps { diff --git a/src/components/GalleryManager/index.tsx b/src/components/GalleryManager/index.tsx index 99ae343..b322884 100644 --- a/src/components/GalleryManager/index.tsx +++ b/src/components/GalleryManager/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { GalleryImage } from '../../types'; import { GalleryGrid } from './GalleryGrid'; import { ImageForm } from './ImageForm'; diff --git a/src/components/Header/CitySelector.tsx b/src/components/Header/CitySelector.tsx new file mode 100644 index 0000000..11330ef --- /dev/null +++ b/src/components/Header/CitySelector.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ChevronDown } from 'lucide-react'; +import { City } from '../../types'; + +interface CitySelectorProps { + cities: City[]; + currentCity: string | null; +} + +export function CitySelector({ cities, currentCity }: CitySelectorProps) { + const navigate = useNavigate(); + + const handleCityChange = (event: React.ChangeEvent) => { + const city = event.target.value; + const params = new URLSearchParams(window.location.search); + + if (city) { + params.set('city', city); + } else { + params.delete('city'); + } + + navigate(`/?${params.toString()}`); + }; + + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/src/components/Header/Logo.tsx b/src/components/Header/Logo.tsx new file mode 100644 index 0000000..f4b6588 --- /dev/null +++ b/src/components/Header/Logo.tsx @@ -0,0 +1,11 @@ +import { Link } from 'react-router-dom'; +import { Palette } from 'lucide-react'; + +export function Logo() { + return ( + + + CultureScope + + ); +} \ No newline at end of file diff --git a/src/components/Header/MobileMenu.tsx b/src/components/Header/MobileMenu.tsx new file mode 100644 index 0000000..49d9639 --- /dev/null +++ b/src/components/Header/MobileMenu.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Category, City } from '../../types'; + +interface MobileMenuProps { + isOpen: boolean; + categories: Category[]; + cities: City[]; + currentCategory: string | null; + currentCity: string | null; + onCityChange: (event: React.ChangeEvent) => void; +} + +export function MobileMenu({ + isOpen, + categories, + cities, + currentCategory, + currentCity, + onCityChange +}: MobileMenuProps) { + if (!isOpen) return null; + + return ( +
+
+
+

City

+ +
+ +
+

Categories

+
+ + All Categories + + {categories.map((category) => ( + + {category} + + ))} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Header/Navigation.tsx b/src/components/Header/Navigation.tsx new file mode 100644 index 0000000..dc857bb --- /dev/null +++ b/src/components/Header/Navigation.tsx @@ -0,0 +1,39 @@ +import { Link, useLocation } from 'react-router-dom'; +import { Category } from '../../types'; + +interface NavigationProps { + categories: Category[]; + currentCategory: string | null; + currentCity: string | null; +} + +export function Navigation({ categories, currentCategory, currentCity }: NavigationProps) { + return ( +
+ + + All Categories + + {categories.map((category) => ( + + {category} + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/components/Header/SearchBar.tsx b/src/components/Header/SearchBar.tsx new file mode 100644 index 0000000..00ae56a --- /dev/null +++ b/src/components/Header/SearchBar.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Search } from 'lucide-react'; + +export function SearchBar() { + const [searchQuery, setSearchQuery] = useState(''); + const navigate = useNavigate(); + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery.trim()) { + navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`); + } + }; + + const handleSearchKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch(e); + } + }; + + return ( +
+ setSearchQuery(e.target.value)} + onKeyPress={handleSearchKeyPress} + className="w-40 lg:w-60 pl-10 pr-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + +
+ ); +} \ No newline at end of file diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx new file mode 100644 index 0000000..ee989fe --- /dev/null +++ b/src/components/Header/index.tsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import { Menu, X, Bookmark } from 'lucide-react'; +import { Link, useLocation } from 'react-router-dom'; +import { Category, City } from '../../types'; +import { Logo } from './Logo'; +import { SearchBar } from './SearchBar'; +import { Navigation } from './Navigation'; +import { CitySelector } from './CitySelector'; +import { MobileMenu } from './MobileMenu'; +import { useBookmarkStore } from '../../stores/bookmarkStore'; + +const categories: Category[] = ['Film', 'Theater', 'Music', 'Sports', 'Art', 'Legends', 'Anniversaries', 'Memory']; +const cities: City[] = ['New York', 'London']; + +export function Header() { + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const currentCategory = searchParams.get('category'); + const currentCity = searchParams.get('city'); + const { bookmarks } = useBookmarkStore(); + + const handleCityChange = (event: React.ChangeEvent) => { + const city = event.target.value; + const params = new URLSearchParams(location.search); + + if (city) { + params.set('city', city); + } else { + params.delete('city'); + } + + window.location.href = `/?${params.toString()}`; + }; + + return ( +
+
+
+
+ +
+ +
+
+ + + +
+ + + + {bookmarks.length > 0 && ( + + {bookmarks.length} + + )} + +
+
+
+ + +
+ ); +} \ No newline at end of file diff --git a/src/components/ImageUpload/ImageDropzone.tsx b/src/components/ImageUpload/ImageDropzone.tsx new file mode 100644 index 0000000..ea1218f --- /dev/null +++ b/src/components/ImageUpload/ImageDropzone.tsx @@ -0,0 +1,45 @@ +import React, { useCallback } from 'react'; +import { useDropzone } from 'react-dropzone'; +import { ImagePlus } from 'lucide-react'; + +interface ImageDropzoneProps { + onDrop: (files: File[]) => void; + disabled?: boolean; +} + +export function ImageDropzone({ onDrop, disabled = false }: ImageDropzoneProps) { + const handleDrop = useCallback((acceptedFiles: File[]) => { + onDrop(acceptedFiles); + }, [onDrop]); + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop: handleDrop, + accept: { + 'image/*': ['.png', '.jpg', '.jpeg', '.webp'] + }, + disabled, + maxSize: 10 * 1024 * 1024 // 10MB + }); + + return ( +
+ + +

+ {isDragActive ? ( + 'Drop the image here' + ) : ( + 'Drag & drop an image here, or click to select' + )} +

+

+ PNG, JPG, JPEG or WebP up to 10MB +

+
+ ); +} \ No newline at end of file diff --git a/src/components/ImageUpload/ImageUploader.tsx b/src/components/ImageUpload/ImageUploader.tsx new file mode 100644 index 0000000..2b30e40 --- /dev/null +++ b/src/components/ImageUpload/ImageUploader.tsx @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; +import { ImageDropzone } from './ImageDropzone'; +import { ResolutionSelect } from './ResolutionSelect'; +import { UploadProgress } from './UploadProgress'; +import { imageResolutions } from '../../config/imageResolutions'; +import { uploadImage } from '../../services/imageService'; +import { ImageUploadProgress } from '../../types/image'; + +interface ImageUploaderProps { + onUploadComplete: (imageUrl: string) => void; +} + +export function ImageUploader({ onUploadComplete }: ImageUploaderProps) { + const [selectedResolution, setSelectedResolution] = useState('medium'); + const [uploadProgress, setUploadProgress] = useState>({}); + + const handleDrop = async (files: File[]) => { + for (const file of files) { + try { + setUploadProgress(prev => ({ + ...prev, + [file.name]: { progress: 0, status: 'uploading' } + })); + + const resolution = imageResolutions.find(r => r.id === selectedResolution); + if (!resolution) throw new Error('Invalid resolution selected'); + + const uploadedImage = await uploadImage(file, resolution, (progress) => { + setUploadProgress(prev => ({ + ...prev, + [file.name]: { progress, status: 'uploading' } + })); + }); + + setUploadProgress(prev => ({ + ...prev, + [file.name]: { progress: 100, status: 'complete' } + })); + + onUploadComplete(uploadedImage.url); + } catch (error) { + setUploadProgress(prev => ({ + ...prev, + [file.name]: { + progress: 0, + status: 'error', + error: error instanceof Error ? error.message : 'Upload failed' + } + })); + } + } + }; + + const isUploading = Object.values(uploadProgress).some( + p => p.status === 'uploading' || p.status === 'processing' + ); + + return ( +
+ + + + + {Object.entries(uploadProgress).map(([fileName, progress]) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/components/ImageUpload/ResolutionSelect.tsx b/src/components/ImageUpload/ResolutionSelect.tsx new file mode 100644 index 0000000..1623fd3 --- /dev/null +++ b/src/components/ImageUpload/ResolutionSelect.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { ImageResolution } from '../../types/image'; + +interface ResolutionSelectProps { + resolutions: ImageResolution[]; + selectedResolution: string; + onChange: (resolutionId: string) => void; + disabled?: boolean; +} + +export function ResolutionSelect({ + resolutions, + selectedResolution, + onChange, + disabled = false +}: ResolutionSelectProps) { + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/src/components/ImageUpload/UploadProgress.tsx b/src/components/ImageUpload/UploadProgress.tsx new file mode 100644 index 0000000..78b5b03 --- /dev/null +++ b/src/components/ImageUpload/UploadProgress.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { ImageUploadProgress } from '../../types/image'; +import { CheckCircle, AlertCircle, Loader } from 'lucide-react'; + +interface UploadProgressProps { + progress: ImageUploadProgress; + fileName: string; +} + +export function UploadProgress({ progress, fileName }: UploadProgressProps) { + const getStatusIcon = () => { + switch (progress.status) { + case 'complete': + return ; + case 'error': + return ; + default: + return ; + } + }; + + const getStatusText = () => { + switch (progress.status) { + case 'uploading': + return 'Uploading...'; + case 'processing': + return 'Processing...'; + case 'complete': + return 'Upload complete'; + case 'error': + return progress.error || 'Upload failed'; + } + }; + + return ( +
+ {getStatusIcon()} +
+

+ {fileName} +

+

{getStatusText()}

+
+ {(progress.status === 'uploading' || progress.status === 'processing') && ( +
+
+
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index 3526aa9..ee6177d 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ChevronLeft, ChevronRight } from 'lucide-react'; interface PaginationProps { diff --git a/src/components/ReactionButtons.tsx b/src/components/ReactionButtons.tsx index e09e546..4d93d58 100644 --- a/src/components/ReactionButtons.tsx +++ b/src/components/ReactionButtons.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import { ThumbsUp, ThumbsDown } from 'lucide-react'; interface ReactionButtonsProps { likes: number; dislikes: number; - userReaction: 'like' | 'dislike' | null; + userReaction: 'like' | 'dislike' | null | undefined; onReact: (reaction: 'like' | 'dislike') => void; } diff --git a/src/config/imageResolutions.ts b/src/config/imageResolutions.ts new file mode 100644 index 0000000..be035ac --- /dev/null +++ b/src/config/imageResolutions.ts @@ -0,0 +1,28 @@ +import { ImageResolution } from '../types/image'; + +export const imageResolutions: ImageResolution[] = [ + { + id: 'thumbnail', + width: 300, + height: 300, + label: 'Thumbnail (300x300)' + }, + { + id: 'medium', + width: 800, + height: 600, + label: 'Medium (800x600)' + }, + { + id: 'large', + width: 1920, + height: 1080, + label: 'Large (1920x1080)' + }, + { + id: 'original', + width: 0, // 0 means keep original dimensions + height: 0, + label: 'Original Size' + } +]; \ No newline at end of file diff --git a/src/data/mock.ts b/src/data/mock.ts index 28c47c1..a84ca61 100644 --- a/src/data/mock.ts +++ b/src/data/mock.ts @@ -1,4 +1,4 @@ -import { Article, Author, City } from '../types'; +import { Article, Author } from '../types'; export const authors: Author[] = [ { @@ -88,7 +88,7 @@ export const articles: Article[] = [ title: 'Modern Literature in the Digital Age', excerpt: 'How e-books and digital platforms are changing the way we consume and create literature.', content: 'The digital revolution has transformed the literary landscape, offering new ways for authors to reach readers and for stories to be told.', - category: 'Literature', + category: 'Art', city: 'New York', author: authors[0], coverImage: 'https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?auto=format&fit=crop&q=80&w=2070', diff --git a/src/hooks/useGallery.ts b/src/hooks/useGallery.ts new file mode 100644 index 0000000..c2c7ac9 --- /dev/null +++ b/src/hooks/useGallery.ts @@ -0,0 +1,94 @@ +import { useState, useEffect } from 'react'; +import { GalleryImage } from '../types'; +import { galleryService } from '../services/galleryService'; +import { uploadImage } from '../services/imageService'; +import { ImageResolution } from '../types/image'; + +export function useGallery(articleId: string) { + const [images, setImages] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + loadGallery(); + }, [articleId]); + + const loadGallery = async () => { + try { + setLoading(true); + const galleryImages = await galleryService.getArticleGallery(articleId); + setImages(galleryImages); + setError(null); + } catch (err) { + setError('Failed to load gallery images'); + console.error('Error loading gallery:', err); + } finally { + setLoading(false); + } + }; + + const addImage = async (file: File, resolution: ImageResolution) => { + try { + const uploadedImage = await uploadImage(file, resolution); + const galleryImage = await galleryService.createImage(articleId, { + url: uploadedImage.url, + caption: '', + alt: file.name, + width: uploadedImage.width, + height: uploadedImage.height, + size: uploadedImage.size, + format: uploadedImage.format + }); + setImages([...images, galleryImage]); + return galleryImage; + } catch (err) { + console.error('Error adding image:', err); + throw err; + } + }; + + const updateImage = async (id: string, updates: Partial) => { + try { + const updatedImage = await galleryService.updateImage(id, updates); + setImages(images.map(img => img.id === id ? updatedImage : img)); + return updatedImage; + } catch (err) { + console.error('Error updating image:', err); + throw err; + } + }; + + const deleteImage = async (id: string) => { + try { + await galleryService.deleteImage(id); + setImages(images.filter(img => img.id !== id)); + } catch (err) { + console.error('Error deleting image:', err); + throw err; + } + }; + + const reorderImages = async (imageIds: string[]) => { + try { + await galleryService.reorderImages(articleId, imageIds); + const reorderedImages = imageIds.map(id => + images.find(img => img.id === id)! + ); + setImages(reorderedImages); + } catch (err) { + console.error('Error reordering images:', err); + throw err; + } + }; + + return { + images, + loading, + error, + addImage, + updateImage, + deleteImage, + reorderImages, + refresh: loadGallery + }; +} \ No newline at end of file diff --git a/src/pages/ArticlePage.tsx b/src/pages/ArticlePage.tsx index 41b9add..befc18d 100644 --- a/src/pages/ArticlePage.tsx +++ b/src/pages/ArticlePage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; import { ArrowLeft, Clock, Share2, Bookmark } from 'lucide-react'; import { Header } from '../components/Header'; diff --git a/src/pages/BookmarksPage.tsx b/src/pages/BookmarksPage.tsx new file mode 100644 index 0000000..7f7ddfe --- /dev/null +++ b/src/pages/BookmarksPage.tsx @@ -0,0 +1,38 @@ +import { Header } from '../components/Header'; +import { ArticleCard } from '../components/ArticleCard'; +import { useBookmarkStore } from '../stores/bookmarkStore'; +import { Bookmark } from 'lucide-react'; + +export function BookmarksPage() { + const { bookmarks } = useBookmarkStore(); + + return ( +
+
+
+
+ +

Your Bookmarks

+
+ + {bookmarks.length > 0 ? ( +
+ {bookmarks.map((article) => ( + + ))} +
+ ) : ( +
+ +

+ No bookmarks yet +

+

+ Start bookmarking articles you want to read later +

+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index edc9528..9b6576d 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useSearchParams } from 'react-router-dom'; import { Header } from '../components/Header'; import { FeaturedSection } from '../components/FeaturedSection'; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index a4c7a49..3443bd0 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,14 +1,8 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useAuthStore } from '../stores/authStore'; import { AlertCircle } from 'lucide-react'; -// Temporary admin credentials - DEVELOPMENT ONLY -const TEMP_ADMIN = { - email: 'admin@culturescope.com', - password: 'admin123' -}; - export function LoginPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -27,7 +21,7 @@ export function LoginPage() { await login(email, password); // Pass the admin user object navigate(from, { replace: true }); } catch (err) { - setError('An error occurred while logging in'); + setError('An error occurred while logging in ' + err); } }; @@ -36,7 +30,7 @@ export function LoginPage() {

- Sign in to your account + Войдите в свой эккаунт

{/* Temporary credentials notice */}
@@ -109,7 +103,7 @@ export function LoginPage() { type="submit" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > - Sign in + Вход
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index 21b072f..fdc1bdf 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Header } from '../components/Header'; import { ArticleCard } from '../components/ArticleCard'; diff --git a/src/services/galleryService.ts b/src/services/galleryService.ts new file mode 100644 index 0000000..ecb15a0 --- /dev/null +++ b/src/services/galleryService.ts @@ -0,0 +1,27 @@ +import axios from '../utils/api'; +import { GalleryImage } from '../types'; + +export const galleryService = { + createImage: async (articleId: string, imageData: Omit) => { + const { data } = await axios.post(`/gallery/article/${articleId}`, imageData); + return data; + }, + + updateImage: async (id: string, updates: Partial) => { + const { data } = await axios.put(`/gallery/${id}`, updates); + return data; + }, + + deleteImage: async (id: string) => { + await axios.delete(`/gallery/${id}`); + }, + + reorderImages: async (articleId: string, imageIds: string[]) => { + await axios.post(`/gallery/article/${articleId}/reorder`, { imageIds }); + }, + + getArticleGallery: async (articleId: string) => { + const { data } = await axios.get(`/gallery/article/${articleId}`); + return data as GalleryImage[]; + } +}; \ No newline at end of file diff --git a/src/services/imageService.ts b/src/services/imageService.ts new file mode 100644 index 0000000..53d0b4c --- /dev/null +++ b/src/services/imageService.ts @@ -0,0 +1,32 @@ +import axios from '../utils/api'; +import { ImageResolution, UploadedImage } from '../types/image'; + +export async function uploadImage( + file: File, + resolution: ImageResolution, + onProgress?: (progress: number) => void +): Promise { + // Get pre-signed URL for S3 upload + const { data: { uploadUrl, imageId } } = await axios.post('/images/upload-url', { + fileName: file.name, + fileType: file.type, + resolution: resolution.id + }); + + // Upload to S3 + await axios.put(uploadUrl, file, { + headers: { + 'Content-Type': file.type + }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total); + onProgress?.(progress); + } + } + }); + + // Get the processed image details + const { data: image } = await axios.get(`/images/${imageId}`); + return image; +} \ No newline at end of file diff --git a/src/stores/bookmarkStore.ts b/src/stores/bookmarkStore.ts new file mode 100644 index 0000000..eb34300 --- /dev/null +++ b/src/stores/bookmarkStore.ts @@ -0,0 +1,36 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { Article } from '../types'; + +interface BookmarkState { + bookmarks: Article[]; + addBookmark: (article: Article) => void; + removeBookmark: (articleId: string) => void; + isBookmarked: (articleId: string) => boolean; +} + +export const useBookmarkStore = create()( + persist( + (set, get) => ({ + bookmarks: [], + addBookmark: (article) => { + if (!get().isBookmarked(article.id)) { + set((state) => ({ + bookmarks: [...state.bookmarks, article], + })); + } + }, + removeBookmark: (articleId) => { + set((state) => ({ + bookmarks: state.bookmarks.filter((article) => article.id !== articleId), + })); + }, + isBookmarked: (articleId) => { + return get().bookmarks.some((article) => article.id === articleId); + }, + }), + { + name: 'bookmarks-storage', + } + ) +); \ No newline at end of file diff --git a/src/types/image.ts b/src/types/image.ts new file mode 100644 index 0000000..dc123fe --- /dev/null +++ b/src/types/image.ts @@ -0,0 +1,21 @@ +export interface ImageResolution { + id: string; + width: number; + height: number; + label: string; +} + +export interface UploadedImage { + id: string; + url: string; + width: number; + height: number; + size: number; + format: string; +} + +export interface ImageUploadProgress { + progress: number; + status: 'uploading' | 'processing' | 'complete' | 'error'; + error?: string; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1ffef60..6cd2095 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,5 +3,12 @@ "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } - ] + ], + "compilerOptions": { + "target": "esnext", // Compile to a modern ECMAScript version that supports ES modules + "module": "esnext", // Use ES module syntax for module code generation + "moduleResolution": "node", // Use Node.js style module resolution + "esModuleInterop": true, // Enables default imports from modules with no default export + "outDir": "./dist" // Output directory for compiled files + } } diff --git a/vite.config.ts b/vite.config.ts index 1876030..8b7f910 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ exclude: ['@prisma/client', 'lucide-react'], }, server: { + host: true, proxy: { '/api': { target: 'http://localhost:5000',