fix:uplodig of assets and configured file server
This commit is contained in:
Generated
+402
-3
@@ -8,6 +8,7 @@
|
||||
"name": "maskan-pim-backend",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1111.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"compression": "^1.7.5",
|
||||
"cookie-parser": "^1.4.7",
|
||||
@@ -86,6 +87,314 @@
|
||||
"openapi-types": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/checksums": {
|
||||
"version": "3.1000.28",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.28.tgz",
|
||||
"integrity": "sha512-VCpnmyHQ1IH49ni3LXnQj7DPr7rmcJmzYeiCkYdCcfgNtkvOj38cdcL9lapBWoItZWFACJPFJlymqC7/gem3Gw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-s3": {
|
||||
"version": "3.1111.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1111.0.tgz",
|
||||
"integrity": "sha512-VnLT6aSTN8tWl/NsXUysXNZor7wQBp9CRwufo7kt8cwGXvHLZ0S/cV1K9WFcREGboVYSo3NGQ3ZvU7LRidh2aQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/checksums": "^3.1000.28",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.80",
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.74",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.977.8",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.8.tgz",
|
||||
"integrity": "sha512-7+Kcrkvrk9lM/m7jRhHpT4jCdvzGHsuaSRbF8TdzzkY1mRzp/Ogwf9c7H29k4gGhey0BBWhCWr16+t0J61gwmg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@aws-sdk/xml-builder": "^3.972.39",
|
||||
"@aws/lambda-invoke-store": "^0.3.0",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/signature-v4": "^5.6.12",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"bowser": "^2.11.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.972.69",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.69.tgz",
|
||||
"integrity": "sha512-AreCFzcB4kH2HF9031Ot0jSJr3KXvRg6e8uDeub20JEVdZU3Bv0sTq1plc7VsT3KiqutlzH7l0j50UcCWHUioA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.972.71",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.71.tgz",
|
||||
"integrity": "sha512-A8ObcqVmDMnk4F9NozZ7JwmUu9Q4xyBJkmyq1C5U+wNM9ht9J7+EuuyabsLWXZnOoTqFaJuYBYTKf5CTipkEjA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.973.14",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.14.tgz",
|
||||
"integrity": "sha512-7c+Wti2LsERNWMfm7ySz3/6RPopFW3Nmn7s63Xpcq6R/tRuY5hpvkHA2xVgi5ukJbvok9l0IDtVEvqTtg+X7dw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.71",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.76",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.13",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.75",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/credential-provider-imds": "^4.4.16",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-login": {
|
||||
"version": "3.972.76",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.76.tgz",
|
||||
"integrity": "sha512-LVixwOnEJfrrfKHeZjBA8pIMTZjNDq8ak8VpcoWUuCJDrSnBNU8POJksULMgvN089P0MXtQYH2Zs627/MK1K0g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.972.80",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.80.tgz",
|
||||
"integrity": "sha512-bE2qh8ww4iClO1jHsBXdOE8FUgzDbdxbyorNjSCoPSkQd51k3jODItuPZfuwcLHZqDXsH+bI4AMHhqtuyR7mSg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.71",
|
||||
"@aws-sdk/credential-provider-ini": "^3.973.14",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.13",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.75",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/credential-provider-imds": "^4.4.16",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.972.69",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.69.tgz",
|
||||
"integrity": "sha512-9kpTNdZTrcqXTfhxM7fgl9Z68ek3Fu5oe3Yf+A/pJGibEqpgZxz2tSY7SinmyCIU2PJ+ygY4FPoBBnLpocMtrQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.973.13",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.13.tgz",
|
||||
"integrity": "sha512-Oc81qauMPzUoTnAS2YKpNwY6sY/LUyQTEeaf6yP197WMxkEBQfcKLR1MFpD7+pNTubXnfkH6gwpji+Gc7iyD2Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/token-providers": "3.1111.0",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.972.75",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.75.tgz",
|
||||
"integrity": "sha512-YPN6uoGDgjjjeVFZrcOeCJqmB6zpXoeeNgIjqe+DexJaWqdjVfCCe+VAZwli9Z2h8KhFW8oxkO39emQ1tyz/Mw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||
"version": "3.972.74",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.74.tgz",
|
||||
"integrity": "sha512-2lzoV2z2QO5KJZYGOCnIZ1WVQgzMECvwuzr1xb034a++8QW4U4eGrmC2u4yg1xvNv4TLL/Uv5DLyuAiw0b9z7Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.997.43",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.43.tgz",
|
||||
"integrity": "sha512-bit+VpqWNyi3wHxFoTsTliNXimCSL2r2OeDTm7ZrG+YsTZ2D7ofDJ6r/t9PVBn80i6/v0X2h9Tgw6QP2MAKfPw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||
"version": "3.996.45",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.45.tgz",
|
||||
"integrity": "sha512-bBuyztukzXq6plzFGHAWiQt0QXo+HL8b8lX5cFTzkez/74PtS1c0qPFCIVuHkyoT+miH2qOjAcm1/yoro2ESPA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/signature-v4": "^5.6.12",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.1111.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1111.0.tgz",
|
||||
"integrity": "sha512-JfljgoVtl+s3Qy21n9a7Z48uCQaOXcN74KJ3TEQfPoB293GrXFSt6HSQJF1sTZ8c/5QedEvd3NjJQMO4u9qa5A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/types": {
|
||||
"version": "3.974.4",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.4.tgz",
|
||||
"integrity": "sha512-dSFDNG00MEz0/xl5gxL62giLd1iYyJsTxZ1I1DOj6lC+bbgLB4TRsYClJg3b62dhXT1uATzsTNXPnC+33EJV3A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder": {
|
||||
"version": "3.972.39",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.39.tgz",
|
||||
"integrity": "sha512-FTti8DS5MMWXNUWiRwXAJeYS+0GHHiMy0+7XOhcwk63ILHmfS2UFy2z/HNpZCSOJJ3P3dnWY6hfYNW3DF0nXUA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws/lambda-invoke-store": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
|
||||
"integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
|
||||
@@ -156,6 +465,87 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@smithy/core": {
|
||||
"version": "3.33.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.2.tgz",
|
||||
"integrity": "sha512-CUGXpnPkVdjUCbix+83sWLW9VFgQOm44MDOx/ihITJMAnOZKvL8YYIc7DR9pP/tZ8CIRvMiON/TucvygqbHO3w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.17.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/credential-provider-imds": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz",
|
||||
"integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.33.2",
|
||||
"@smithy/types": "^4.17.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/fetch-http-handler": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz",
|
||||
"integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.33.2",
|
||||
"@smithy/types": "^4.17.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/node-http-handler": {
|
||||
"version": "4.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.11.2.tgz",
|
||||
"integrity": "sha512-avwAh9HM3h2lcfjvP3zYIZGf+XVgLQ91wOJ2qoFbNpW1UZeZb33aGlhTZvtkANHfcGhJroRY64525OjfgOg30g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.33.2",
|
||||
"@smithy/types": "^4.17.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/signature-v4": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.2.tgz",
|
||||
"integrity": "sha512-P7Ki6px6OOrxVtx8K7nLmyx4SlXUW/uTKDdMG44UHefmPGSRMBKe2v+TM59WdLcpUIrBrnuCsIqiM2MbsZjmhw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.33.2",
|
||||
"@smithy/types": "^4.17.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/types": {
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.17.2.tgz",
|
||||
"integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@so-ric/colorspace": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
|
||||
@@ -269,7 +659,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -463,6 +852,12 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"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/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
@@ -1207,7 +1602,6 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -2414,7 +2808,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz",
|
||||
"integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.14.0",
|
||||
"pg-pool": "^3.14.0",
|
||||
@@ -3526,6 +3919,12 @@
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"db:seed:undo:test": "cross-env NODE_ENV=test sequelize-cli db:seed:undo:all --config src/shared/config/database.config.cjs --seeders-path src/seeders --models-path src/shared/database"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1111.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"compression": "^1.7.5",
|
||||
"cookie-parser": "^1.4.7",
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import service from './asset.service.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { models } from '../../../shared/database/models.js';
|
||||
import { S3Service } from '../../../shared/services/s3.service.js';
|
||||
import { extractMetadata } from '../../../shared/utils/metadataExtractor.js';
|
||||
|
||||
|
||||
export class AssetController {
|
||||
getAll = async (req, res, next) => {
|
||||
@@ -58,13 +64,47 @@ export class AssetController {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ success: false, message: 'No file uploaded' });
|
||||
}
|
||||
|
||||
let fileUrl = `/uploads/${req.file.filename}`;
|
||||
let metadata = {};
|
||||
let isS3 = false;
|
||||
|
||||
if (req.context && req.context.tenantId) {
|
||||
const storageConfig = await models.FileServerConfig.findOne({
|
||||
where: { tenant_id: req.context.tenantId, status: 'active' }
|
||||
});
|
||||
|
||||
if (storageConfig) {
|
||||
isS3 = true;
|
||||
const localPath = req.file.path;
|
||||
const s3Key = `uploads/${req.file.filename}`;
|
||||
|
||||
// 1. Extract metadata from local file before upload
|
||||
metadata = extractMetadata(localPath);
|
||||
|
||||
// 2. Upload to S3
|
||||
fileUrl = await S3Service.uploadFile(storageConfig, localPath, s3Key, req.file.mimetype);
|
||||
|
||||
// 3. Delete local file
|
||||
await fs.promises.unlink(localPath).catch(err => {
|
||||
console.error('Failed to delete temp file:', localPath, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
name: req.file.originalname,
|
||||
file_url: `/uploads/${req.file.filename}`,
|
||||
file_url: fileUrl,
|
||||
file_size: req.file.size,
|
||||
mime_type: req.file.mimetype
|
||||
mime_type: req.file.mimetype,
|
||||
...(isS3 ? {
|
||||
checksum: metadata.checksum,
|
||||
width: metadata.width,
|
||||
height: metadata.height,
|
||||
extension: metadata.extension
|
||||
} : {})
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -77,9 +117,50 @@ export class AssetController {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ success: false, message: 'No file uploaded' });
|
||||
}
|
||||
|
||||
let fileUrl = `/uploads/${req.file.filename}`;
|
||||
let metadata = {};
|
||||
let isS3 = false;
|
||||
|
||||
if (req.context && req.context.tenantId) {
|
||||
const storageConfig = await models.FileServerConfig.findOne({
|
||||
where: { tenant_id: req.context.tenantId, status: 'active' }
|
||||
});
|
||||
|
||||
if (storageConfig) {
|
||||
isS3 = true;
|
||||
const localPath = req.file.path;
|
||||
const s3Key = `uploads/${req.file.filename}`;
|
||||
|
||||
// 1. Extract metadata from local file before upload
|
||||
metadata = extractMetadata(localPath);
|
||||
|
||||
// 2. Upload to S3
|
||||
fileUrl = await S3Service.uploadFile(storageConfig, localPath, s3Key, req.file.mimetype);
|
||||
|
||||
// 3. Delete local file
|
||||
await fs.promises.unlink(localPath).catch(err => {
|
||||
console.error('Failed to delete temp file:', localPath, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const fileData = {
|
||||
file_url: fileUrl,
|
||||
file_name: req.file.originalname,
|
||||
file_size: req.file.size,
|
||||
mime_type: req.file.mimetype,
|
||||
...(isS3 ? {
|
||||
checksum: metadata.checksum,
|
||||
width: metadata.width,
|
||||
height: metadata.height,
|
||||
extension: metadata.extension
|
||||
} : {})
|
||||
};
|
||||
|
||||
const record = await service.replaceFile(
|
||||
req.params.id,
|
||||
{ file_url: `/uploads/${req.file.filename}` },
|
||||
fileData,
|
||||
req.context
|
||||
);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
@@ -88,6 +169,7 @@ export class AssetController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
archive = async (req, res, next) => {
|
||||
try {
|
||||
const record = await service.archive(req.params.id, req.context);
|
||||
|
||||
@@ -78,21 +78,29 @@ export class AssetService {
|
||||
// 2. Set file-based fields if file is being uploaded
|
||||
let metadata = {};
|
||||
if (data.file_url) {
|
||||
// Handle path resolution safely for any absolute or relative URLs
|
||||
let cleanPath = data.file_url;
|
||||
if (cleanPath.includes('/uploads/')) {
|
||||
cleanPath = 'uploads/' + cleanPath.split('/uploads/')[1];
|
||||
if (data.file_url.startsWith('http://') || data.file_url.startsWith('https://')) {
|
||||
// Remote S3 file - use fields passed directly in data
|
||||
data.file_name = data.file_name || path.basename(data.file_url) || 'asset_file';
|
||||
data.extension = data.extension || path.extname(data.file_url).replace('.', '') || 'bin';
|
||||
data.file_size = data.file_size || 0;
|
||||
data.checksum = data.checksum || `CHK-${Date.now()}-${Math.round(Math.random() * 1000)}`;
|
||||
} else {
|
||||
// Handle path resolution safely for any absolute or relative URLs
|
||||
let cleanPath = data.file_url;
|
||||
if (cleanPath.includes('/uploads/')) {
|
||||
cleanPath = 'uploads/' + cleanPath.split('/uploads/')[1];
|
||||
}
|
||||
const absolutePath = path.resolve(cleanPath);
|
||||
|
||||
metadata = extractMetadata(absolutePath);
|
||||
data.file_name = data.file_name || path.basename(absolutePath) || 'asset_file';
|
||||
data.extension = metadata.extension || path.extname(absolutePath).replace('.', '') || 'bin';
|
||||
data.file_size = metadata.file_size || data.file_size || 0;
|
||||
data.checksum = metadata.checksum || `CHK-${Date.now()}-${Math.round(Math.random() * 1000)}`;
|
||||
data.width = metadata.width || null;
|
||||
data.height = metadata.height || null;
|
||||
data.page_count = metadata.page_count || null;
|
||||
}
|
||||
const absolutePath = path.resolve(cleanPath);
|
||||
|
||||
metadata = extractMetadata(absolutePath);
|
||||
data.file_name = data.file_name || path.basename(absolutePath) || 'asset_file';
|
||||
data.extension = metadata.extension || path.extname(absolutePath).replace('.', '') || 'bin';
|
||||
data.file_size = metadata.file_size || data.file_size || 0;
|
||||
data.checksum = metadata.checksum || `CHK-${Date.now()}-${Math.round(Math.random() * 1000)}`;
|
||||
data.width = metadata.width || null;
|
||||
data.height = metadata.height || null;
|
||||
data.page_count = metadata.page_count || null;
|
||||
|
||||
// Check duplicates by checksum
|
||||
if (data.checksum) {
|
||||
@@ -106,6 +114,7 @@ export class AssetService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enforce non-nullable fallback defaults
|
||||
data.file_name = data.file_name || 'asset_file';
|
||||
data.file_url = data.file_url || '/uploads/placeholder';
|
||||
@@ -232,10 +241,28 @@ export class AssetService {
|
||||
}
|
||||
|
||||
// Check duplicates of new file checksum
|
||||
const relativePath = fileData.file_url.replace(/^\/uploads\//, 'uploads/');
|
||||
const absolutePath = path.resolve(relativePath);
|
||||
const metadata = extractMetadata(absolutePath);
|
||||
const newChecksum = metadata.checksum;
|
||||
let metadata = {};
|
||||
let newChecksum;
|
||||
let absolutePath = '';
|
||||
|
||||
if (fileData.file_url.startsWith('http://') || fileData.file_url.startsWith('https://')) {
|
||||
// Remote S3 file - use fields pre-calculated and passed in fileData
|
||||
metadata = {
|
||||
file_size: fileData.file_size || 0,
|
||||
extension: fileData.extension || path.extname(fileData.file_url).replace('.', '') || 'bin',
|
||||
checksum: fileData.checksum || `CHK-${Date.now()}`,
|
||||
width: fileData.width || null,
|
||||
height: fileData.height || null,
|
||||
page_count: fileData.extension === 'pdf' ? 1 : null
|
||||
};
|
||||
newChecksum = metadata.checksum;
|
||||
} else {
|
||||
// Local file path
|
||||
const relativePath = fileData.file_url.replace(/^\/uploads\//, 'uploads/');
|
||||
absolutePath = path.resolve(relativePath);
|
||||
metadata = extractMetadata(absolutePath);
|
||||
newChecksum = metadata.checksum;
|
||||
}
|
||||
|
||||
const duplicate = await models.Asset.findOne({
|
||||
where: { checksum: newChecksum, status: 'active', id: { [Op.ne]: id } },
|
||||
@@ -257,7 +284,7 @@ export class AssetService {
|
||||
// 2. Update asset with new file details
|
||||
const updateData = {
|
||||
file_url: fileData.file_url,
|
||||
file_name: path.basename(absolutePath),
|
||||
file_name: fileData.file_name || (absolutePath ? path.basename(absolutePath) : path.basename(fileData.file_url)),
|
||||
extension: metadata.extension,
|
||||
file_size: metadata.file_size,
|
||||
checksum: newChecksum,
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import service from '../services/fileServerConfig.service.js';
|
||||
|
||||
export class FileServerConfigController {
|
||||
get = async (req, res, next) => {
|
||||
try {
|
||||
if (!req.context.tenantId) {
|
||||
return res.status(200).json({ success: true, data: null });
|
||||
}
|
||||
const config = await service.getByTenantId(req.context.tenantId);
|
||||
if (config) {
|
||||
// Strip or mask keys for response safety
|
||||
config.secret_key = config.secret_key ? '********************' : null;
|
||||
config.access_key = config.access_key ? '********************' : null;
|
||||
}
|
||||
return res.status(200).json({ success: true, data: config });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
save = async (req, res, next) => {
|
||||
try {
|
||||
if (!req.context.tenantId) {
|
||||
return res.status(400).json({ success: false, message: 'Platform administrators cannot configure a tenant-scoped file server. Please log in as a tenant administrator.' });
|
||||
}
|
||||
let configData = { ...req.body };
|
||||
const currentConfig = await service.getByTenantId(req.context.tenantId);
|
||||
|
||||
// If access or secret key is submitted as masked, preserve the existing value
|
||||
if (currentConfig) {
|
||||
if (configData.access_key === '********************') {
|
||||
configData.access_key = currentConfig.access_key;
|
||||
}
|
||||
if (configData.secret_key === '********************') {
|
||||
configData.secret_key = currentConfig.secret_key;
|
||||
}
|
||||
}
|
||||
|
||||
const config = await service.saveConfig(req.context.tenantId, configData, req.context);
|
||||
|
||||
// Return masked values
|
||||
if (config) {
|
||||
config.secret_key = '********************';
|
||||
config.access_key = '********************';
|
||||
}
|
||||
|
||||
return res.status(200).json({ success: true, data: config });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
test = async (req, res, next) => {
|
||||
try {
|
||||
let configData = { ...req.body };
|
||||
|
||||
if (req.context.tenantId) {
|
||||
const currentConfig = await service.getByTenantId(req.context.tenantId);
|
||||
// If access or secret key is submitted as masked, preserve the existing value for testing
|
||||
if (currentConfig) {
|
||||
if (configData.access_key === '********************') {
|
||||
configData.access_key = currentConfig.access_key;
|
||||
}
|
||||
if (configData.secret_key === '********************') {
|
||||
configData.secret_key = currentConfig.secret_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await service.testConnection(configData);
|
||||
return res.status(200).json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
delete = async (req, res, next) => {
|
||||
try {
|
||||
if (!req.context.tenantId) {
|
||||
return res.status(400).json({ success: false, message: 'No tenant context to delete config' });
|
||||
}
|
||||
await service.deleteConfig(req.context.tenantId, req.context);
|
||||
return res.status(200).json({ success: true, message: 'File server configuration deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileServerConfigController();
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class FileServerConfig extends Model {
|
||||
static associate(models) {
|
||||
FileServerConfig.belongsTo(models.Tenant, {
|
||||
foreignKey: 'tenant_id',
|
||||
as: 'tenant',
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
FileServerConfig.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
tenant_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
provider: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 's3'
|
||||
},
|
||||
endpoint: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
access_key: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
secret_key: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
bucket_name: {
|
||||
type: DataTypes.STRING(150),
|
||||
allowNull: false
|
||||
},
|
||||
region: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'active'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'FileServerConfig',
|
||||
tableName: 'file_server_configs',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return FileServerConfig;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { models } from '../../../../shared/database/models.js';
|
||||
|
||||
export class FileServerConfigRepository {
|
||||
async findByTenantId(tenantId, options = {}) {
|
||||
return await models.FileServerConfig.findOne({
|
||||
where: { tenant_id: tenantId },
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await models.FileServerConfig.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await models.FileServerConfig.findByPk(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await models.FileServerConfig.findByPk(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileServerConfigRepository();
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Router } from 'express';
|
||||
import controller from '../controllers/fileServerConfig.controller.js';
|
||||
import { authenticate } from '../../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../../shared/middleware/permission.middleware.js';
|
||||
import { saveValidation, testValidation } from '../validators/fileServerConfig.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['settings.file_server']),
|
||||
controller.get
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['settings.file_server']),
|
||||
saveValidation,
|
||||
validate,
|
||||
controller.save
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/test',
|
||||
authenticate,
|
||||
authorize(['settings.file_server']),
|
||||
testValidation,
|
||||
validate,
|
||||
controller.test
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['settings.file_server']),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,97 @@
|
||||
import repository from '../repositories/fileServerConfig.repository.js';
|
||||
import { S3Service } from '../../../../shared/services/s3.service.js';
|
||||
import { AuditService } from '../../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../../utils/helpers/ApiError.utils.js';
|
||||
|
||||
export class FileServerConfigService {
|
||||
async getByTenantId(tenantId) {
|
||||
if (!tenantId) {
|
||||
throw new ApiError(400, 'Tenant context is missing');
|
||||
}
|
||||
const record = await repository.findByTenantId(tenantId);
|
||||
return record;
|
||||
}
|
||||
|
||||
async saveConfig(tenantId, data, userContext = {}) {
|
||||
if (!tenantId) {
|
||||
throw new ApiError(400, 'Tenant context is missing');
|
||||
}
|
||||
|
||||
let record = await repository.findByTenantId(tenantId);
|
||||
|
||||
if (record) {
|
||||
// Update existing config
|
||||
record = await repository.update(record.id, {
|
||||
provider: data.provider,
|
||||
endpoint: data.endpoint,
|
||||
access_key: data.access_key,
|
||||
secret_key: data.secret_key,
|
||||
bucket_name: data.bucket_name,
|
||||
region: data.region || null,
|
||||
status: data.status || 'active'
|
||||
});
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE_FILE_SERVER',
|
||||
resource: 'FileServerConfig',
|
||||
resourceId: record.id,
|
||||
userId: userContext.userId || 'system',
|
||||
details: { provider: data.provider, endpoint: data.endpoint, bucket_name: data.bucket_name }
|
||||
});
|
||||
} else {
|
||||
// Create new config
|
||||
record = await repository.create({
|
||||
tenant_id: tenantId,
|
||||
provider: data.provider,
|
||||
endpoint: data.endpoint,
|
||||
access_key: data.access_key,
|
||||
secret_key: data.secret_key,
|
||||
bucket_name: data.bucket_name,
|
||||
region: data.region || null,
|
||||
status: data.status || 'active'
|
||||
});
|
||||
|
||||
await AuditService.log({
|
||||
action: 'CREATE_FILE_SERVER',
|
||||
resource: 'FileServerConfig',
|
||||
resourceId: record.id,
|
||||
userId: userContext.userId || 'system',
|
||||
details: { provider: data.provider, endpoint: data.endpoint, bucket_name: data.bucket_name }
|
||||
});
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async testConnection(data) {
|
||||
try {
|
||||
await S3Service.testConnection(data);
|
||||
return { success: true, message: 'Connection to file server was successful' };
|
||||
} catch (error) {
|
||||
throw new ApiError(400, `Connection test failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteConfig(tenantId, userContext = {}) {
|
||||
if (!tenantId) {
|
||||
throw new ApiError(400, 'Tenant context is missing');
|
||||
}
|
||||
const record = await repository.findByTenantId(tenantId);
|
||||
if (!record) {
|
||||
throw new ApiError(404, 'No file server configuration found');
|
||||
}
|
||||
|
||||
await repository.delete(record.id);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE_FILE_SERVER',
|
||||
resource: 'FileServerConfig',
|
||||
resourceId: record.id,
|
||||
userId: userContext.userId || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileServerConfigService();
|
||||
@@ -0,0 +1,61 @@
|
||||
import { body } from 'express-validator';
|
||||
|
||||
export const saveValidation = [
|
||||
body('provider')
|
||||
.isIn(['s3', 'minio'])
|
||||
.withMessage('Provider must be either s3 or minio'),
|
||||
body('endpoint')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Endpoint is required'),
|
||||
body('access_key')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Access Key is required'),
|
||||
body('secret_key')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Secret Key is required'),
|
||||
body('bucket_name')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Bucket Name is required'),
|
||||
body('region')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
];
|
||||
|
||||
export const testValidation = [
|
||||
body('provider')
|
||||
.isIn(['s3', 'minio'])
|
||||
.withMessage('Provider must be either s3 or minio'),
|
||||
body('endpoint')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Endpoint is required'),
|
||||
body('access_key')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Access Key is required'),
|
||||
body('secret_key')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Secret Key is required'),
|
||||
body('bucket_name')
|
||||
.isString()
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.withMessage('Bucket Name is required'),
|
||||
body('region')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
];
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Router } from 'express';
|
||||
import settingsRouter from './settings/setting.routes.js';
|
||||
import themeRouter from './theme/routes/theme.routes.js';
|
||||
import fileServerRouter from './fileServer/routes/fileServerConfig.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/settings/theme', themeRouter);
|
||||
router.use('/settings/file-server', fileServerRouter);
|
||||
router.use('/settings', settingsRouter);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// 1. Create file_server_configs table
|
||||
await queryInterface.createTable('file_server_configs', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
tenant_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'tenants',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
provider: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 's3'
|
||||
},
|
||||
endpoint: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
access_key: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
secret_key: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
bucket_name: {
|
||||
type: Sequelize.STRING(150),
|
||||
allowNull: false
|
||||
},
|
||||
region: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
created_at: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
}
|
||||
});
|
||||
|
||||
// Add unique index on tenant_id to allow only one file server configuration per tenant
|
||||
await queryInterface.addIndex('file_server_configs', ['tenant_id'], {
|
||||
unique: true,
|
||||
name: 'idx_file_server_configs_tenant_id'
|
||||
});
|
||||
|
||||
// 2. Insert permission node for settings.file_server
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO permission_nodes (node_code, node_name, node_type, parent_id, node_level, display_order,
|
||||
can_view, can_create, can_edit, can_delete, can_alter, can_export, can_import, created_at, updated_at)
|
||||
VALUES (
|
||||
'settings.file_server',
|
||||
'File Server Settings',
|
||||
'feature',
|
||||
(SELECT id FROM permission_nodes WHERE node_code = 'settings' LIMIT 1),
|
||||
2,
|
||||
36,
|
||||
true, true, true, true, false, false, false,
|
||||
NOW(), NOW()
|
||||
)
|
||||
ON CONFLICT (node_code) DO NOTHING;
|
||||
`);
|
||||
|
||||
// 3. Map permission to SUPER_ADMIN role (full access)
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO role_permissions (role_id, node_id, can_view, can_create, can_edit, can_delete, can_alter, can_export, can_import, created_at, updated_at)
|
||||
SELECT
|
||||
(SELECT id FROM roles WHERE role_code = 'SUPER_ADMIN' LIMIT 1),
|
||||
(SELECT id FROM permission_nodes WHERE node_code = 'settings.file_server' LIMIT 1),
|
||||
true, true, true, true, true, true, true,
|
||||
NOW(), NOW()
|
||||
ON CONFLICT DO NOTHING;
|
||||
`);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
// Remove role_permissions assignments
|
||||
await queryInterface.sequelize.query(`
|
||||
DELETE FROM role_permissions
|
||||
WHERE node_id = (SELECT id FROM permission_nodes WHERE node_code = 'settings.file_server' LIMIT 1);
|
||||
`);
|
||||
|
||||
// Remove permission node
|
||||
await queryInterface.sequelize.query(`
|
||||
DELETE FROM permission_nodes WHERE node_code = 'settings.file_server';
|
||||
`);
|
||||
|
||||
// Drop table
|
||||
await queryInterface.dropTable('file_server_configs');
|
||||
}
|
||||
};
|
||||
@@ -30,6 +30,11 @@ export const associateModels = () => {
|
||||
models.Tenant.hasMany(models.Role, { foreignKey: 'tenant_id', as: 'roles' });
|
||||
}
|
||||
|
||||
if (models.Tenant && models.FileServerConfig) {
|
||||
models.Tenant.hasOne(models.FileServerConfig, { foreignKey: 'tenant_id', as: 'fileServerConfig' });
|
||||
}
|
||||
|
||||
|
||||
if (models.Role && models.PermissionNode && models.RolePermission) {
|
||||
models.Role.belongsToMany(models.PermissionNode, {
|
||||
through: models.RolePermission,
|
||||
@@ -134,6 +139,8 @@ import unitModelInit from '../../features/brands/units/unit.model.js';
|
||||
import settingModelInit from '../../features/settings/settings/setting.model.js';
|
||||
import auditLogModelInit from '../../features/auditLogs/auditLogs/auditLog.model.js';
|
||||
import userThemeModelInit from '../../features/settings/theme/models/userTheme.model.js';
|
||||
import fileServerConfigModelInit from '../../features/settings/fileServer/models/fileServerConfig.model.js';
|
||||
|
||||
|
||||
// Attribute Management Models
|
||||
import attributeModelInit from '../../features/attributes/attributes/attribute.model.js';
|
||||
@@ -199,6 +206,8 @@ export const initializeDatabaseModels = () => {
|
||||
registerModel('Setting', settingModelInit);
|
||||
registerModel('AuditLog', auditLogModelInit);
|
||||
registerModel('UserTheme', userThemeModelInit);
|
||||
registerModel('FileServerConfig', fileServerConfigModelInit);
|
||||
|
||||
|
||||
// Attribute Management
|
||||
registerModel('Attribute', attributeModelInit);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3';
|
||||
import fs from 'fs';
|
||||
|
||||
export class S3Service {
|
||||
/**
|
||||
* Uploads a file from local disk to an S3/MinIO bucket.
|
||||
* @param {Object} config - The file server credentials (endpoint, access_key, secret_key, bucket_name, region)
|
||||
* @param {string} localFilePath - Path to the local file
|
||||
* @param {string} s3Key - Key/path under which the file should be stored in S3
|
||||
* @param {string} mimeType - The MIME type of the file
|
||||
* @returns {Promise<string>} The public access URL of the uploaded file
|
||||
*/
|
||||
static async uploadFile(config, localFilePath, s3Key, mimeType) {
|
||||
const client = new S3Client({
|
||||
endpoint: config.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: config.access_key,
|
||||
secretAccessKey: config.secret_key
|
||||
},
|
||||
region: config.region || 'us-east-1',
|
||||
forcePathStyle: true
|
||||
});
|
||||
|
||||
const fileStream = fs.createReadStream(localFilePath);
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: config.bucket_name,
|
||||
Key: s3Key,
|
||||
Body: fileStream,
|
||||
ContentType: mimeType
|
||||
});
|
||||
|
||||
await client.send(command);
|
||||
|
||||
// Format the returned URL
|
||||
const baseEndpoint = config.endpoint.endsWith('/') ? config.endpoint.slice(0, -1) : config.endpoint;
|
||||
return `${baseEndpoint}/${config.bucket_name}/${s3Key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a connection to S3/MinIO using the provided credentials.
|
||||
* @param {Object} config - The credentials to test
|
||||
* @returns {Promise<boolean>} True if connection succeeded
|
||||
*/
|
||||
static async testConnection(config) {
|
||||
const client = new S3Client({
|
||||
endpoint: config.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: config.access_key,
|
||||
secretAccessKey: config.secret_key
|
||||
},
|
||||
region: config.region || 'us-east-1',
|
||||
forcePathStyle: true
|
||||
});
|
||||
|
||||
const command = new HeadBucketCommand({
|
||||
Bucket: config.bucket_name
|
||||
});
|
||||
|
||||
await client.send(command);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
Reference in New Issue
Block a user