diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..738b83a --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# ParsPack S3 Storage +BUCKET_NAME=your_bucket_name +BUCKET_REGION=us-west-2 +PARSPACK_S3_ENDPOINT=https://your_bucket_name.parspack.net +BUCKET_PUBLIC_URL=https://your_bucket_name.parspack.net +BUCKET_ACCESS_KEY=your_access_key +BUCKET_SECRET_KEY=your_secret_key diff --git a/package-lock.json b/package-lock.json index 6226dcf..cff8161 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "ostandari-portal", "version": "1.0.0", "dependencies": { + "@aws-sdk/client-s3": "^3.1072.0", "@coreui/coreui": "^3.2.2", "@coreui/icons": "^2.0.0-beta.5", "@coreui/utils": "^1.3.1", @@ -26,6 +27,7 @@ "convertapi": "^1.14.0", "core-js": "^3.9.1", "cors": "^2.8.5", + "dotenv": "^17.4.2", "element-ui": "^2.15.1", "express": "^4.17.1", "express-fileupload": "^1.2.1", @@ -122,6 +124,436 @@ "openapi-types": ">=7" } }, + "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/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-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-sdk/checksums": { + "version": "3.1000.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.7.tgz", + "integrity": "sha512-qh0fG/RtrFztst4+vn1HZehAvAhr5Jlq/WMP7e5KvvfF16oNVBc9CDNVdxdm19vzOY2x0qiDMFCRjhxQAusGWQ==", + "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.974.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1072.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1072.0.tgz", + "integrity": "sha512-W5+7uklGSWJYsK1v/pUsv6i5/VR4xKXzcqPq7xHubhWPcRfhV4v/93XRkjjn57nJheDBjvzD/nVxBqhHOLuxCw==", + "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/core": "^3.974.22", + "@aws-sdk/credential-provider-node": "^3.972.57", + "@aws-sdk/middleware-flexible-checksums": "^3.974.32", + "@aws-sdk/middleware-sdk-s3": "^3.972.53", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.22.tgz", + "integrity": "sha512-YofH63shc6YRdXjz80BJkpJW+Bkn0Cuu2dn4Rv7s9G2Idt58tgtzQEWxrR2xVljlVfIBeUjPuULnSVYLke3sUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.13", + "@aws-sdk/xml-builder": "^3.972.30", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.6", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.48.tgz", + "integrity": "sha512-h6FEC95fbexUd6zxm4PdgS82bTcI2PRtUb2ZwMipb/Xr8bPwtf0G8rBo2jp7NA24Mbx2JA8/WingiYpA9RCCyw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.50", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.50.tgz", + "integrity": "sha512-lJO3OLpjvz5m/RSBQmsG/CEUGsvCy5ruxKwPQaOCqxqCMuyYT2BZwQUTDZVVwqQ9LrZKuK24JSa6r31hL/tvkg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.55.tgz", + "integrity": "sha512-TBoF4buBGYhXjdZAryayY2TrkQj2B2KfE/msG4V53XCt+w0EhEwM2JRjx8p2grJ2C6gtH5++SAwEvGMRdi0yyw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/credential-provider-env": "^3.972.48", + "@aws-sdk/credential-provider-http": "^3.972.50", + "@aws-sdk/credential-provider-login": "^3.972.54", + "@aws-sdk/credential-provider-process": "^3.972.48", + "@aws-sdk/credential-provider-sso": "^3.972.54", + "@aws-sdk/credential-provider-web-identity": "^3.972.54", + "@aws-sdk/nested-clients": "^3.997.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.54.tgz", + "integrity": "sha512-hBWI3wZTdTGiuMfmPts6AWbAjFfRniOQnqx68tc2cQvRKWawFbN9wkLOVPWM1FAOyowZU73mC6Fi+rHSHNyLFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/nested-clients": "^3.997.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.57.tgz", + "integrity": "sha512-u6dClpzNdWf1HGWz4wwhdXi1wiOofCLniM9S4BQQGlLAN9TW7VB+ld5V533GdKrYMaFeBGFqKnj0JCYvynLqwQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.48", + "@aws-sdk/credential-provider-http": "^3.972.50", + "@aws-sdk/credential-provider-ini": "^3.972.55", + "@aws-sdk/credential-provider-process": "^3.972.48", + "@aws-sdk/credential-provider-sso": "^3.972.54", + "@aws-sdk/credential-provider-web-identity": "^3.972.54", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.48.tgz", + "integrity": "sha512-w6VZwojPt12WnEkAUy6Nu4K6sWCbBmR7QX390b0nE6vRvkXbrYr9Lq9VySGkfjiMjpUA87op+J4EgvRmtWIDoQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.54.tgz", + "integrity": "sha512-23uZpIpF2SIFDCa1fcWa202tK4gGeyvX6GIIAjiB8WBsvsVRBMnJ/7dCxHzxf7eZT7GToJg837LDIBnZsl/VUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/nested-clients": "^3.997.22", + "@aws-sdk/token-providers": "3.1071.0", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.54.tgz", + "integrity": "sha512-0Iv5QttS6wcATlodYKgvQj6B9Db51rx7NU9fqu0PoLeS4BIgdYMc/QK4smwLwpm5RFrs02V/eLyEFp3FklvlNQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/nested-clients": "^3.997.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.32", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.32.tgz", + "integrity": "sha512-KhuzFMzUbb3oEj43CdPDbEJ/RG/RkErkmXk3J/LE8OPFNvkCn8PYPMpjOLgzAzvxBacsSyytdWf+R50q0alJ4w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1000.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.53", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.53.tgz", + "integrity": "sha512-keWp6Z5cEIJzPwoCf/WRm0ceAeephPDDivhRsK/xXs2ZYXyypJ2/DL9G1IR0bz/s+iZC0EgzmFV4r7rlvLlxQQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.22.tgz", + "integrity": "sha512-4IwtcYSxEIVw5hcp8ogq0CMbFNZFw7jJUetpfFUhFFeqsa1K8j2Ihg2hnxLyOp3stMZnXda6VzOmPi1AFZQXcg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.35.tgz", + "integrity": "sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.13", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1071.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1071.0.tgz", + "integrity": "sha512-4LDW2Qob6LoLFuqYSYZq2AyTE9koSE9+i+n5UZcm10GpmQOK0zRD9L4uYlzItiTKksIWgC/qMFChAi3RvKYtMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.22", + "@aws-sdk/nested-clients": "^3.997.22", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.13.tgz", + "integrity": "sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.30.tgz", + "integrity": "sha512-StElZPEoBquWwNqw1AcfpzEyZqJvFxouG+mpDNYlcH6ZOrqd2CuIryv+8LV8gNHZUOyKyJF3Dq9vxaXEmDR9TQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.3", + "fast-xml-parser": "5.7.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.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", @@ -149,6 +581,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -2081,6 +2514,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2103,6 +2537,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" } @@ -3207,6 +3642,18 @@ "tslib": "2" } }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3807,6 +4254,18 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/@nuxt/config/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@nuxt/core": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/@nuxt/core/-/core-2.18.1.tgz", @@ -4907,6 +5366,7 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4952,6 +5412,126 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@smithy/core": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.25.1.tgz", + "integrity": "sha512-zpDbpXBCBsxfLtG2GEUyfgvHvSFrw5CwDZSNzL0v52gx/c3oPlPbm+7W7num8xs6vyiUBn+bvYPHcQDOXZynCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.1.tgz", + "integrity": "sha512-TSAF5NHgxEsllbErYWbK8aLnl5L601NGc5VYJlSPsKnf3YlkhdoBN+geGcaU00oiw2OK3QO5LA3QNXiiWhCidQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.25.1", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.5.1.tgz", + "integrity": "sha512-96JrD1q71anokymx9Iblb+zKmNQYNstlV/25A9ZYIJ2A0rp1r7/GZAIm0bDWSmVvz3DpNOCZuabzsiL+w0UHhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.25.1", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/@smithy/node-http-handler": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.8.1.tgz", + "integrity": "sha512-emtXvoky671puri18ETf64AFIQUGIEA093F2drXpBgB0OGnBLjcwNR3CA2mYu62IAqNsS56xa5lnTxAgPq7cjw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.25.1", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.5.1.tgz", + "integrity": "sha512-X9rVls3En0z3NtrmguTmpRM0/NqtWUxBjal6fcAkwtsub+gOdLZ6kD+V7xhUgFMGdG14bHbZ7M5QjaRI1+DatQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.25.1", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.15.0.tgz", + "integrity": "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/@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/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -5523,6 +6103,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5673,6 +6254,18 @@ "node": ">= 8" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -6439,6 +7032,12 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -6719,6 +7318,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -6741,32 +7341,6 @@ "node": ">=0.6.19" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -6854,6 +7428,18 @@ } } }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -7652,6 +8238,7 @@ "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -8747,9 +9334,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -9341,6 +9928,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9579,6 +10167,43 @@ "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -12121,7 +12746,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-cookie": { "version": "2.2.1", @@ -13296,6 +13922,7 @@ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.22.tgz", "integrity": "sha512-p51k/c4X/MfqeQ3I1ranlDiggLzNumZrTDD9CeezHwZxt2/btf+YZD7MCe07RAY2NgFYVMayq6jMamw02Jmf9w==", "license": "MIT", + "peer": true, "dependencies": { "@types/bson": "1.x || 4.0.x", "@types/mongodb": "^3.5.27", @@ -15130,6 +15757,21 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -15204,7 +15846,8 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/picocolors": { "version": "1.1.1", @@ -15319,6 +15962,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -16511,6 +17155,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -18721,6 +19366,21 @@ "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "license": "MIT" }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, "node_modules/style-resources-loader": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/style-resources-loader/-/style-resources-loader-1.5.0.tgz", @@ -19444,7 +20104,8 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tty-browserify": { "version": "0.0.0", @@ -19666,17 +20327,6 @@ } } }, - "node_modules/unctx/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -19829,17 +20479,6 @@ } } }, - "node_modules/unimport/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -20299,6 +20938,7 @@ "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-sfc": "2.7.16", "csstype": "^3.1.0" @@ -20997,6 +21637,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz", "integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", @@ -21116,6 +21757,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -22010,6 +22652,21 @@ } } }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xss": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", diff --git a/package.json b/package.json index 2f388f9..43ba022 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "generate": "nuxt generate" }, "dependencies": { + "@aws-sdk/client-s3": "^3.1072.0", "@coreui/coreui": "^3.2.2", "@coreui/icons": "^2.0.0-beta.5", "@coreui/utils": "^1.3.1", @@ -29,6 +30,7 @@ "convertapi": "^1.14.0", "core-js": "^3.9.1", "cors": "^2.8.5", + "dotenv": "^17.4.2", "element-ui": "^2.15.1", "express": "^4.17.1", "express-fileupload": "^1.2.1", diff --git a/server/controllers/newsController.js b/server/controllers/newsController.js index 2a1b44e..53f8439 100644 --- a/server/controllers/newsController.js +++ b/server/controllers/newsController.js @@ -1,6 +1,5 @@ const News = require("../models/News"); const { body, validationResult } = require("express-validator"); -const sharp = require("sharp"); const { _sr } = require("../plugins/serverResponses"); const { res404, @@ -14,9 +13,13 @@ const fs = require("fs"); const NewsFile = require("../models/NewsFile"); const Polls = require("../models/Polls"); const moment = require("moment-jalaali"); +const { + getStoredImageName, + deleteNewsImage, + uploadProcessedImage, +} = require("../plugins/newsImageStorage"); ////// variables -const noCover = "/img/no-cover.jpg"; const coverNewsWidth = 1920; const coverNewsHeight = 1080; const coverQuality = 100; @@ -111,31 +114,23 @@ const handleFileUploads = async (req, res, targetNews) => { if (req.files.cover) { const image = req.files.cover; - if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) { + if (!validateNewsImage(image)) { throw new Error(_sr["fa"].format.image); } - // حذف کاور قدیمی در صورت وجود - if (targetNews.cover && targetNews.cover !== noCover) { - const oldCoverPath = `./static/uploads/images/news/${targetNews.cover}`; - if (fs.existsSync(oldCoverPath)) { - fs.unlinkSync(oldCoverPath); - } - } + await deleteNewsImage(targetNews, "cover"); const imageName = `news_cover_${timestamp}_${targetNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(coverNewsWidth, coverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: coverQuality, - }) - .toFile(`./static/uploads/images/news/${imageName}`); + await processAndUploadNewsImage( + image.data, + imageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); targetNews.cover = imageName; hasCover = true; @@ -143,28 +138,20 @@ const handleFileUploads = async (req, res, targetNews) => { throw new Error(e?.message); } - // اگر thumbCover ارسال نشده، از همین تصویر thumbCover بسازیم if (!req.files.thumbCover) { - if (targetNews.thumbCover && targetNews.thumbCover !== noCover) { - const oldThumbPath = `./static/uploads/images/news/${targetNews.thumbCover}`; - if (fs.existsSync(oldThumbPath)) { - fs.unlinkSync(oldThumbPath); - } - } + await deleteNewsImage(targetNews, "thumbCover"); const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: thumbCoverQuality, - }) - .toFile(`./static/uploads/images/news/${thumbImageName}`); + await processAndUploadNewsImage( + image.data, + thumbImageName, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); targetNews.thumbCover = thumbImageName; hasThumb = true; @@ -176,31 +163,23 @@ const handleFileUploads = async (req, res, targetNews) => { if (req.files.thumbCover) { const image = req.files.thumbCover; - if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) { + if (!validateNewsImage(image)) { throw new Error(_sr["fa"].format.image); } - // حذف thumb قدیمی در صورت وجود - if (targetNews.thumbCover && targetNews.thumbCover !== noCover) { - const oldThumbPath = `./static/uploads/images/news/${targetNews.thumbCover}`; - if (fs.existsSync(oldThumbPath)) { - fs.unlinkSync(oldThumbPath); - } - } + await deleteNewsImage(targetNews, "thumbCover"); const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: thumbCoverQuality, - }) - .toFile(`./static/uploads/images/news/${thumbImageName}`); + await processAndUploadNewsImage( + image.data, + thumbImageName, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); targetNews.thumbCover = thumbImageName; hasThumb = true; @@ -208,28 +187,20 @@ const handleFileUploads = async (req, res, targetNews) => { throw new Error(e?.message); } - // اگر cover ارسال نشده، از همین تصویر cover بسازیم if (!req.files.cover) { - if (targetNews.cover && targetNews.cover !== noCover) { - const oldCoverPath = `./static/uploads/images/news/${targetNews.cover}`; - if (fs.existsSync(oldCoverPath)) { - fs.unlinkSync(oldCoverPath); - } - } + await deleteNewsImage(targetNews, "cover"); const coverImageName = `news_cover_${timestamp}_${targetNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(coverNewsWidth, coverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: coverQuality, - }) - .toFile(`./static/uploads/images/news/${coverImageName}`); + await processAndUploadNewsImage( + image.data, + coverImageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); targetNews.cover = coverImageName; hasCover = true; @@ -239,14 +210,24 @@ const handleFileUploads = async (req, res, targetNews) => { } } - // به‌روزرسانی وضعیت خبر - خبر کامل است اگر حداقل یکی از تصاویر آپلود شده باشد یا قبلاً موجود باشد - const currentHasCover = targetNews.cover && targetNews.cover !== noCover; - const currentHasThumb = - targetNews.thumbCover && targetNews.thumbCover !== noCover; + const currentHasCover = getStoredImageName(targetNews, "cover"); + const currentHasThumb = getStoredImageName(targetNews, "thumbCover"); targetNews.newsStatus = hasCover || hasThumb || currentHasCover || currentHasThumb; }; +const processAndUploadNewsImage = async ( + imageData, + imageName, + width, + height, + quality +) => uploadProcessedImage(imageData, imageName, width, height, quality); + +const validateNewsImage = (image) => { + return _sr.supportedImageFormats.includes(image.mimetype.split("/")[1]); +}; + /////////////////////////////////////////////////////////////////////// admin module.exports.addNews = [ [ @@ -488,9 +469,7 @@ module.exports.addNews = [ if (req.files.cover) { const image = req.files.cover; - if ( - !_sr.supportedImageFormats.includes(image.mimetype.split("/")[1]) - ) { + if (!validateNewsImage(image)) { return res.status(422).json({ validation: { cover: { @@ -503,15 +482,13 @@ module.exports.addNews = [ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(coverNewsWidth, coverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: coverQuality, - }) - .toFile(`./static/uploads/images/news/${imageName}`); + await processAndUploadNewsImage( + image.data, + imageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); createNews.cover = imageName; hasCover = true; @@ -519,21 +496,18 @@ module.exports.addNews = [ return res500(res, e?.message); } - // اگر thumbCover ارسال نشده، از همین تصویر thumbCover بسازیم if (!req.files.thumbCover) { const thumbImageName = `news_thumb_${timestamp}_${createNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: thumbCoverQuality, - }) - .toFile(`./static/uploads/images/news/${thumbImageName}`); + await processAndUploadNewsImage( + image.data, + thumbImageName, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); createNews.thumbCover = thumbImageName; hasThumb = true; @@ -545,9 +519,7 @@ module.exports.addNews = [ if (req.files.thumbCover) { const image = req.files.thumbCover; - if ( - !_sr.supportedImageFormats.includes(image.mimetype.split("/")[1]) - ) { + if (!validateNewsImage(image)) { return res.status(422).json({ validation: { thumbCover: { @@ -560,15 +532,13 @@ module.exports.addNews = [ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: thumbCoverQuality, - }) - .toFile(`./static/uploads/images/news/${thumbImageName}`); + await processAndUploadNewsImage( + image.data, + thumbImageName, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); createNews.thumbCover = thumbImageName; hasThumb = true; @@ -576,21 +546,18 @@ module.exports.addNews = [ return res500(res, e?.message); } - // اگر cover ارسال نشده، از همین تصویر cover بسازیم if (!req.files.cover) { const coverImageName = `news_cover_${timestamp}_${createNews._id}.${ image.mimetype.split("/")[1] }`; try { - const target = sharp(image.data); - await target - .resize(coverNewsWidth, coverNewsHeight, { - fit: "cover", - }) - .jpeg({ - quality: coverQuality, - }) - .toFile(`./static/uploads/images/news/${coverImageName}`); + await processAndUploadNewsImage( + image.data, + coverImageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); createNews.cover = coverImageName; hasCover = true; @@ -600,7 +567,6 @@ module.exports.addNews = [ } } - // خبر کامل است اگر حداقل یکی از تصاویر آپلود شده باشد createNews.newsStatus = hasCover || hasThumb; await createNews.save(); } @@ -987,30 +953,13 @@ module.exports.deletOneNews = [ return res500(res, `شما اجازه حذف این خبر را ندارید`); } - if (targetNews.cover !== noCover) { - oldPic = `./static${targetNews.cover}`; - - if (fs.existsSync(oldPic)) { - fs.unlink(oldPic, (error) => { - if (error) return res500(res, error?.message); - }); - } - } - - if (targetNews.thumbCover !== noCover) { - oldPic = `./static${targetNews.thumbCover}`; - - if (fs.existsSync(oldPic)) { - fs.unlink(oldPic, (error) => { - if (error) return res500(res, error?.message); - }); - } - } + await deleteNewsImage(targetNews, "cover"); + await deleteNewsImage(targetNews, "thumbCover"); await targetNews.deleteOne(); await Comment.deleteMany({ modelRef: targetNews._id }); - x; + return res.json(targetNews); } catch (error) { return res500(res, error); diff --git a/server/controllers/newsFileController.js b/server/controllers/newsFileController.js index f08d479..fae551d 100644 --- a/server/controllers/newsFileController.js +++ b/server/controllers/newsFileController.js @@ -4,11 +4,12 @@ const {_sr} = require('../plugins/serverResponses') const {res404, res500} = require('../plugins/controllersHelperFunctions') const News = require('../models/News') const Category = require('../models/Category') -const sharp = require('sharp') -const fs = require('fs') +const { + deleteNewsImage, + uploadProcessedImage, +} = require('../plugins/newsImageStorage') ////// variables -const noCover = '/img/no-cover.jpg' const limit = 5; const limitDetails = 11; const titleFilterRegex = new RegExp('/', 'g') @@ -187,19 +188,23 @@ module.exports.addNewsFile = [ }) const imageName = `news_${Date.now()}_${createNewsFile._id}.${image.mimetype.split('/')[1]}`; try { - const target = sharp(image.data) - await target - .resize(coverNewsWidth, coverNewsHeight, {fit: 'cover'}) - .jpeg({quality: coverQuality}) - .toFile(`./static/uploads/images/news/${imageName}`); + await uploadProcessedImage( + image.data, + imageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); + + await uploadProcessedImage( + image.data, + `thumb_${imageName}`, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); createNewsFile.cover = imageName; - - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {fit: 'cover'}) - .jpeg({quality: thumbCoverQuality}) - .toFile(`./static/uploads/images/news/thumb_${imageName}`); - createNewsFile.thumbCover = `thumb_${imageName}`; await createNewsFile.save(); @@ -375,39 +380,24 @@ module.exports.updateNewsFile = [ } const imageName = `news_${Date.now()}_${targetNewsFile._id}.${image.mimetype.split('/')[1]}`; try { - const target = sharp(image.data) - await target - .resize(coverNewsWidth, coverNewsHeight, {fit: 'cover'}) - .jpeg({quality: coverQuality}) - .toFile(`./static/uploads/images/news/${imageName}`); + await deleteNewsImage(targetNewsFile, "cover"); + await deleteNewsImage(targetNewsFile, "thumbCover"); - await target - .resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {fit: 'cover'}) - .jpeg({quality: thumbCoverQuality}) - .toFile(`./static/uploads/images/news/thumb_${imageName}`); + await uploadProcessedImage( + image.data, + imageName, + coverNewsWidth, + coverNewsHeight, + coverQuality + ); - /** - * if exist old pic delete it from file - */ - if (targetNewsFile.cover !== noCover) { - let oldPic = `./static${targetNewsFile.cover}`; - - if (await fs.existsSync(oldPic)) { - fs.unlink(oldPic, (err) => { - if (err) return res500(res, err?.message); - }); - } - } - - if (targetNewsFile.thumbCover !== noCover) { - let oldPic = `./static${targetNewsFile.thumbCover}`; - - if (await fs.existsSync(oldPic)) { - fs.unlink(oldPic, (err) => { - if (err) return res500(res, err?.message); - }); - } - } + await uploadProcessedImage( + image.data, + `thumb_${imageName}`, + thumbCoverNewsWidth, + thumbCoverNewsHeight, + thumbCoverQuality + ); targetNewsFile.cover = imageName; targetNewsFile.thumbCover = `thumb_${imageName}`; diff --git a/server/index.js b/server/index.js index 0f54e89..68aa6ac 100644 --- a/server/index.js +++ b/server/index.js @@ -1,3 +1,5 @@ +require("dotenv").config(); + const express = require("express"); const database = require("./database"); const fileUpload = require("express-fileupload"); diff --git a/server/models/News.js b/server/models/News.js index d8d8240..a2b104e 100644 --- a/server/models/News.js +++ b/server/models/News.js @@ -1,9 +1,11 @@ const mongoose = require('mongoose'); const mongoosePaginate = require('mongoose-paginate-v2'); +const s3Storage = require('../plugins/s3Storage'); function image(img) { - if (img) return '/uploads/images/news/' + img - else return '/img/no-cover.jpg' + if (!img) return '/img/no-cover.jpg'; + if (img.startsWith('http://') || img.startsWith('https://')) return img; + return s3Storage.getPublicUrl(`uploads/images/news/${img}`); } function file(file) { diff --git a/server/models/NewsFile.js b/server/models/NewsFile.js index 6259ab9..0c8a8e1 100644 --- a/server/models/NewsFile.js +++ b/server/models/NewsFile.js @@ -1,9 +1,11 @@ const mongoose = require('mongoose'); const mongoosePaginate = require('mongoose-paginate-v2'); +const s3Storage = require('../plugins/s3Storage'); function image(img) { - if (img) return '/uploads/images/news/' + img - else return '/img/no-cover.jpg' + if (!img) return '/img/no-cover.jpg'; + if (img.startsWith('http://') || img.startsWith('https://')) return img; + return s3Storage.getPublicUrl(`uploads/images/news/${img}`); } const NewsFileSchema = mongoose.Schema({ diff --git a/server/plugins/newsImageStorage.js b/server/plugins/newsImageStorage.js new file mode 100644 index 0000000..42f4963 --- /dev/null +++ b/server/plugins/newsImageStorage.js @@ -0,0 +1,46 @@ +const sharp = require("sharp"); +const s3Storage = require("./s3Storage"); + +const NEWS_IMAGE_PREFIX = "uploads/images/news"; + +const getStoredImageName = (doc, field) => + doc.get(field, null, { getters: false }); + +const deleteNewsImage = async (doc, field) => { + const fileName = getStoredImageName(doc, field); + if (!fileName) return; + + try { + await s3Storage.deleteObject(`${NEWS_IMAGE_PREFIX}/${fileName}`); + } catch (error) { + console.log(`Failed to delete ${field} from S3:`, error?.message); + } +}; + +const uploadProcessedImage = async ( + imageData, + imageName, + width, + height, + quality +) => { + const buffer = await sharp(imageData) + .resize(width, height, { fit: "cover" }) + .jpeg({ quality }) + .toBuffer(); + + await s3Storage.uploadBuffer( + `${NEWS_IMAGE_PREFIX}/${imageName}`, + buffer, + "image/jpeg" + ); + + return imageName; +}; + +module.exports = { + NEWS_IMAGE_PREFIX, + getStoredImageName, + deleteNewsImage, + uploadProcessedImage, +}; diff --git a/server/plugins/s3Storage.js b/server/plugins/s3Storage.js new file mode 100644 index 0000000..f16c02e --- /dev/null +++ b/server/plugins/s3Storage.js @@ -0,0 +1,93 @@ +const { + S3Client, + PutObjectCommand, + DeleteObjectCommand, +} = require("@aws-sdk/client-s3"); + +const config = { + bucket: process.env.BUCKET_NAME, + region: process.env.BUCKET_REGION || "us-west-2", + endpoint: process.env.PARSPACK_S3_ENDPOINT, + accessKeyId: process.env.BUCKET_ACCESS_KEY, + secretAccessKey: process.env.BUCKET_SECRET_KEY, + publicUrl: process.env.BUCKET_PUBLIC_URL || process.env.PARSPACK_S3_ENDPOINT, +}; + +let s3Client = null; + +const isConfigured = () => + Boolean( + config.bucket && + config.endpoint && + config.accessKeyId && + config.secretAccessKey + ); + +const getClient = () => { + if (!isConfigured()) { + throw new Error("S3 storage is not configured"); + } + + if (!s3Client) { + s3Client = new S3Client({ + region: config.region, + endpoint: config.endpoint, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + forcePathStyle: true, + }); + } + + return s3Client; +}; + +const normalizeKey = (key) => key.replace(/^\/+/, ""); + +const getPublicUrl = (key) => { + const normalizedKey = normalizeKey(key); + const baseUrl = (config.publicUrl || config.endpoint).replace(/\/+$/, ""); + return `${baseUrl}/${normalizedKey}`; +}; + +const uploadBuffer = async ( + key, + buffer, + contentType = "application/octet-stream" +) => { + const normalizedKey = normalizeKey(key); + + await getClient().send( + new PutObjectCommand({ + Bucket: config.bucket, + Key: normalizedKey, + Body: buffer, + ContentType: contentType, + ACL: "public-read", + }) + ); + + return { + key: normalizedKey, + url: getPublicUrl(normalizedKey), + }; +}; + +const deleteObject = async (key) => { + const normalizedKey = normalizeKey(key); + + await getClient().send( + new DeleteObjectCommand({ + Bucket: config.bucket, + Key: normalizedKey, + }) + ); +}; + +module.exports = { + isConfigured, + getPublicUrl, + uploadBuffer, + deleteObject, +}; diff --git a/server/plugins/sms.js b/server/plugins/sms.js index f83a3fe..7acd2cb 100644 --- a/server/plugins/sms.js +++ b/server/plugins/sms.js @@ -1,18 +1,24 @@ const axios = require("axios"); +const SMS_API_KEY = "QkkNxhyXZ6GtEf1soOTtikomO3mA4LaNQDH8mol8huDIwh00"; + module.exports.sms = async (phoneNumber, message) => { - const smsService = new SmsService("QkkNxhyXZ6GtEf1soOTtikomO3mA4LaNQDH8mol8huDIwh00"); + const smsService = new SmsService(SMS_API_KEY); return smsService.sendMessage(phoneNumber, message); }; + +/** Sends login/verification OTP via sms.ir template (verify endpoint). */ +module.exports.sendOtpSms = async (phoneNumber, otp) => { + const smsService = new SmsService(SMS_API_KEY); + return smsService.sendOTPSms(otp, phoneNumber); +}; + class SmsService { constructor(apiKey) { this.API_URL = "https://api.sms.ir/v1"; - this.OTP_PATTERN = "83604"; + this.OTP_PATTERN = "628767"; this.SMS_API_KEY = apiKey; } - //******************************** */ - - //******************************** */ async sendMessage(phone, message) { try { @@ -32,7 +38,7 @@ class SmsService { console.error("Error in sending SMS", error); } } - //******************************** */ + async getLineNumber() { try { const { data } = await axios.get(`${this.API_URL}/line`, { @@ -45,7 +51,6 @@ class SmsService { console.error("Error in getting line number", error); } } - //******************************** */ async sendOTPSms(otp, phone) { const smsData = {