cdn image hosting support
This commit is contained in:
parent
d2f0d9767b
commit
c065835d7a
8 changed files with 337 additions and 408 deletions
337
backend/package-lock.json
generated
337
backend/package-lock.json
generated
|
|
@ -1,337 +0,0 @@
|
||||||
{
|
|
||||||
"name": "rocks-2many-backend",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"append-field": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
|
|
||||||
},
|
|
||||||
"asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
|
||||||
},
|
|
||||||
"axios": {
|
|
||||||
"version": "1.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
|
||||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
|
||||||
"requires": {
|
|
||||||
"follow-redirects": "^1.15.6",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"proxy-from-env": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"buffer-from": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
|
||||||
},
|
|
||||||
"busboy": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
|
||||||
"requires": {
|
|
||||||
"streamsearch": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"call-bind-apply-helpers": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
||||||
"requires": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"combined-stream": {
|
|
||||||
"version": "1.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
||||||
"requires": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"concat-stream": {
|
|
||||||
"version": "1.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
|
||||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
|
||||||
"requires": {
|
|
||||||
"buffer-from": "^1.0.0",
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"readable-stream": "^2.2.2",
|
|
||||||
"typedarray": "^0.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"core-util-is": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
|
||||||
},
|
|
||||||
"csv-parser": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA=="
|
|
||||||
},
|
|
||||||
"csv-writer": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g=="
|
|
||||||
},
|
|
||||||
"delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
|
||||||
},
|
|
||||||
"dunder-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
||||||
"requires": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"gopd": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es-define-property": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
|
||||||
},
|
|
||||||
"es-errors": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
|
||||||
},
|
|
||||||
"es-object-atoms": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
||||||
"requires": {
|
|
||||||
"es-errors": "^1.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es-set-tostringtag": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
||||||
"requires": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"get-intrinsic": "^1.2.6",
|
|
||||||
"has-tostringtag": "^1.0.2",
|
|
||||||
"hasown": "^2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"follow-redirects": {
|
|
||||||
"version": "1.15.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
|
||||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
|
|
||||||
},
|
|
||||||
"form-data": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
|
||||||
"requires": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"es-set-tostringtag": "^2.1.0",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"function-bind": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
|
||||||
},
|
|
||||||
"get-intrinsic": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
||||||
"requires": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
|
||||||
"es-define-property": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"es-object-atoms": "^1.1.1",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-proto": "^1.0.1",
|
|
||||||
"gopd": "^1.2.0",
|
|
||||||
"has-symbols": "^1.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"math-intrinsics": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"get-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
||||||
"requires": {
|
|
||||||
"dunder-proto": "^1.0.1",
|
|
||||||
"es-object-atoms": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gopd": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
|
|
||||||
},
|
|
||||||
"has-symbols": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
|
||||||
},
|
|
||||||
"has-tostringtag": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
||||||
"requires": {
|
|
||||||
"has-symbols": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hasown": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
|
||||||
"requires": {
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
|
||||||
},
|
|
||||||
"isarray": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
|
||||||
},
|
|
||||||
"math-intrinsics": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
|
||||||
},
|
|
||||||
"media-typer": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
|
||||||
},
|
|
||||||
"mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
|
||||||
},
|
|
||||||
"mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"requires": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimist": {
|
|
||||||
"version": "1.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
|
||||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
|
|
||||||
},
|
|
||||||
"mkdirp": {
|
|
||||||
"version": "0.5.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
|
||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"multer": {
|
|
||||||
"version": "1.4.5-lts.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
|
|
||||||
"integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
|
|
||||||
"requires": {
|
|
||||||
"append-field": "^1.0.0",
|
|
||||||
"busboy": "^1.0.0",
|
|
||||||
"concat-stream": "^1.5.2",
|
|
||||||
"mkdirp": "^0.5.4",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"type-is": "^1.6.4",
|
|
||||||
"xtend": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"object-assign": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
|
||||||
},
|
|
||||||
"process-nextick-args": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
|
||||||
},
|
|
||||||
"proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "2.3.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
|
||||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.3",
|
|
||||||
"isarray": "~1.0.0",
|
|
||||||
"process-nextick-args": "~2.0.0",
|
|
||||||
"safe-buffer": "~5.1.1",
|
|
||||||
"string_decoder": "~1.1.1",
|
|
||||||
"util-deprecate": "~1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
|
||||||
},
|
|
||||||
"slugify": {
|
|
||||||
"version": "1.6.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
|
|
||||||
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="
|
|
||||||
},
|
|
||||||
"streamsearch": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type-is": {
|
|
||||||
"version": "1.6.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
|
||||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
|
||||||
"requires": {
|
|
||||||
"media-typer": "0.3.0",
|
|
||||||
"mime-types": "~2.1.24"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"typedarray": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
|
||||||
},
|
|
||||||
"util-deprecate": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
|
||||||
},
|
|
||||||
"xtend": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,14 +9,17 @@
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.802.0",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"csv-parser": "^3.2.0",
|
"csv-parser": "^3.2.0",
|
||||||
"csv-writer": "^1.6.0",
|
"csv-writer": "^1.6.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"ioredis": "^5.6.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.2",
|
"multer": "^1.4.5-lts.2",
|
||||||
|
"multer-s3": "^3.0.1",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.1",
|
||||||
"pg": "^8.10.0",
|
"pg": "^8.10.0",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,73 @@
|
||||||
const { Pool } = require('pg');
|
const { Pool } = require('pg');
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
|
|
||||||
// Create a pool instance
|
let writePool, readPool;
|
||||||
const pool = new Pool({
|
|
||||||
|
// Always create write pool
|
||||||
|
writePool = new Pool({
|
||||||
user: config.db.user,
|
user: config.db.user,
|
||||||
password: config.db.password,
|
password: config.db.password,
|
||||||
host: config.db.host,
|
host: config.db.host,
|
||||||
port: config.db.port,
|
port: config.db.port,
|
||||||
database: config.db.database
|
database: config.db.database,
|
||||||
|
max: config.db.maxConnections,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function for running queries
|
// Create read pool only in cloud mode
|
||||||
const query = async (text, params) => {
|
if (config.site.deployment === 'cloud') {
|
||||||
|
readPool = new Pool({
|
||||||
|
user: config.db.user,
|
||||||
|
password: config.db.password,
|
||||||
|
host: config.db.readHost,
|
||||||
|
port: config.db.port,
|
||||||
|
database: config.db.database,
|
||||||
|
max: config.db.maxConnections,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// In self-hosted mode, use the same pool for reads and writes
|
||||||
|
readPool = writePool;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Helper function that automatically routes queries
|
||||||
|
const query = async (text, params, options = {}) => {
|
||||||
|
const { forceWrite = false } = options;
|
||||||
|
|
||||||
|
const isWrite = forceWrite ||
|
||||||
|
text.trim().toUpperCase().startsWith('INSERT') ||
|
||||||
|
text.trim().toUpperCase().startsWith('UPDATE') ||
|
||||||
|
text.trim().toUpperCase().startsWith('DELETE') ||
|
||||||
|
text.trim().toUpperCase().includes('FOR UPDATE');
|
||||||
|
|
||||||
|
const pool = isWrite ? writePool : readPool;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const res = await pool.query(text, params);
|
const res = await pool.query(text, params);
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
console.log('Executed query', { text, duration, rows: res.rowCount });
|
|
||||||
|
if (config.site.deployment === 'cloud') {
|
||||||
|
console.log('Executed query', {
|
||||||
|
text,
|
||||||
|
duration,
|
||||||
|
rows: res.rowCount,
|
||||||
|
pool: isWrite ? 'write' : 'read'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Executed query', { text, duration, rows: res.rowCount });
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
query,
|
// Old Query function
|
||||||
pool
|
// const query = async (text, params) => {
|
||||||
};
|
// const start = Date.now();
|
||||||
|
// const res = await pool.query(text, params);
|
||||||
|
// const duration = Date.now() - start;
|
||||||
|
// console.log('Executed query', { text, duration, rows: res.rowCount });
|
||||||
|
// return res;
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { query, pool: writePool };
|
||||||
|
|
@ -13,6 +13,7 @@ const fs = require('fs');
|
||||||
// services
|
// services
|
||||||
const notificationService = require('./services/notificationService');
|
const notificationService = require('./services/notificationService');
|
||||||
const emailService = require('./services/emailService');
|
const emailService = require('./services/emailService');
|
||||||
|
const storageService = require('./services/storageService');
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
const stripePaymentRoutes = require('./routes/stripePayment');
|
const stripePaymentRoutes = require('./routes/stripePayment');
|
||||||
|
|
@ -59,45 +60,46 @@ if (!fs.existsSync(blogImagesDir)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure storage
|
// Configure storage
|
||||||
const storage = multer.diskStorage({
|
// const storage = multer.diskStorage({
|
||||||
destination: (req, file, cb) => {
|
// destination: (req, file, cb) => {
|
||||||
// Determine destination based on upload type
|
// // Determine destination based on upload type
|
||||||
if (req.originalUrl.includes('/product')) {
|
// if (req.originalUrl.includes('/product')) {
|
||||||
cb(null, productImagesDir);
|
// cb(null, productImagesDir);
|
||||||
} else {
|
// } else {
|
||||||
cb(null, uploadsDir);
|
// cb(null, uploadsDir);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
filename: (req, file, cb) => {
|
// filename: (req, file, cb) => {
|
||||||
// Create unique filename with original extension
|
// // Create unique filename with original extension
|
||||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
// const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||||
const fileExt = path.extname(file.originalname);
|
// const fileExt = path.extname(file.originalname);
|
||||||
const safeName = path.basename(file.originalname, fileExt)
|
// const safeName = path.basename(file.originalname, fileExt)
|
||||||
.toLowerCase()
|
// .toLowerCase()
|
||||||
.replace(/[^a-z0-9]/g, '-');
|
// .replace(/[^a-z0-9]/g, '-');
|
||||||
|
|
||||||
cb(null, `${safeName}-${uniqueSuffix}${fileExt}`);
|
// cb(null, `${safeName}-${uniqueSuffix}${fileExt}`);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
// File filter to only allow images
|
// // File filter to only allow images
|
||||||
const fileFilter = (req, file, cb) => {
|
// const fileFilter = (req, file, cb) => {
|
||||||
// Accept only image files
|
// // Accept only image files
|
||||||
if (file.mimetype.startsWith('image/')) {
|
// if (file.mimetype.startsWith('image/')) {
|
||||||
cb(null, true);
|
// cb(null, true);
|
||||||
} else {
|
// } else {
|
||||||
cb(new Error('Only image files are allowed!'), false);
|
// cb(new Error('Only image files are allowed!'), false);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Create the multer instance
|
// Create the multer instance
|
||||||
const upload = multer({
|
// const upload = multer({
|
||||||
storage,
|
// storage,
|
||||||
fileFilter,
|
// fileFilter,
|
||||||
limits: {
|
// limits: {
|
||||||
fileSize: 5 * 1024 * 1024 // 5MB limit
|
// fileSize: 5 * 1024 * 1024 // 5MB limit
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
const upload = storageService.getUploadMiddleware();
|
||||||
|
|
||||||
pool.connect()
|
pool.connect()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
|
@ -197,11 +199,16 @@ app.post('/api/image/upload', upload.single('image'), (req, res) => {
|
||||||
message: 'No image file provided'
|
message: 'No image file provided'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const imagePath = req.file.path ? `/uploads/${req.file.filename}` : req.file.location;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
imagePath: `/uploads/${req.file.filename}`
|
imagePath: storageService.getImageUrl(imagePath)
|
||||||
});
|
});
|
||||||
|
// res.json({
|
||||||
|
// success: true,
|
||||||
|
// imagePath: `/uploads/${req.file.filename}`
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/public-file/:filename', (req, res) => {
|
app.get('/api/public-file/:filename', (req, res) => {
|
||||||
|
|
@ -249,13 +256,22 @@ app.post('/api/image/product', adminAuthMiddleware(pool, query), upload.single('
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the relative path to the image
|
// Get the relative path to the image
|
||||||
const imagePath = `/uploads/products/${req.file.filename}`;
|
const imagePath = req.file.path ?
|
||||||
|
`/uploads/products/${req.file.filename}` :
|
||||||
|
req.file.location;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
imagePath,
|
imagePath: storageService.getImageUrl(imagePath),
|
||||||
filename: req.file.filename
|
filename: req.file.filename || path.basename(req.file.location)
|
||||||
});
|
});
|
||||||
|
// const imagePath = `/uploads/products/${req.file.filename}`;
|
||||||
|
|
||||||
|
// res.json({
|
||||||
|
// success: true,
|
||||||
|
// imagePath,
|
||||||
|
// filename: req.file.filename
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Upload multiple product images (admin only)
|
// Upload multiple product images (admin only)
|
||||||
|
|
@ -269,15 +285,30 @@ app.post('/api/image/products', adminAuthMiddleware(pool, query), upload.array('
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the relative paths to the images
|
// Get the relative paths to the images
|
||||||
const imagePaths = req.files.map(file => ({
|
const imagePaths = req.files.map(file => {
|
||||||
imagePath: `/uploads/products/${file.filename}`,
|
const imagePath = file.path ?
|
||||||
filename: file.filename
|
`/uploads/products/${file.filename}` :
|
||||||
}));
|
file.location;
|
||||||
|
|
||||||
|
return {
|
||||||
|
imagePath: storageService.getImageUrl(imagePath),
|
||||||
|
filename: file.filename || path.basename(file.location)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
images: imagePaths
|
images: imagePaths
|
||||||
});
|
});
|
||||||
|
// const imagePaths = req.files.map(file => ({
|
||||||
|
// imagePath: `/uploads/products/${file.filename}`,
|
||||||
|
// filename: file.filename
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// res.json({
|
||||||
|
// success: true,
|
||||||
|
// images: imagePaths
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete product image (admin only)
|
// Delete product image (admin only)
|
||||||
|
|
@ -293,18 +324,26 @@ app.delete('/api/image/product/:filename', adminAuthMiddleware(pool, query), (re
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = path.join(__dirname, '../public/uploads/products', filename);
|
if (config.site.deployment === 'cloud' && config.site.awsS3Bucket) {
|
||||||
|
// Implementation for S3 deletion would go here
|
||||||
|
// For now, we'll just log and continue
|
||||||
|
console.log('S3 file deletion not implemented yet');
|
||||||
|
} else {
|
||||||
|
// Delete from local filesystem
|
||||||
|
const filePath = path.join(__dirname, '../public/uploads/products', filename);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
error: true,
|
error: true,
|
||||||
message: 'Image not found'
|
message: 'Image not found'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the file
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the file
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -346,6 +385,7 @@ app.use((err, req, res, next) => {
|
||||||
// Start server
|
// Start server
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server running on port ${port} in ${config.environment} environment`);
|
console.log(`Server running on port ${port} in ${config.environment} environment`);
|
||||||
|
console.log(`Deployment mode: ${config.site.deployment || 'self-hosted'}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
@ -68,7 +68,6 @@ module.exports = (pool, query) => {
|
||||||
router.post('/login-request', async (req, res, next) => {
|
router.post('/login-request', async (req, res, next) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
console.log('/login-request')
|
console.log('/login-request')
|
||||||
console.log(JSON.stringify(config, null, 4))
|
|
||||||
try {
|
try {
|
||||||
// Check if user exists
|
// Check if user exists
|
||||||
const userResult = await query(
|
const userResult = await query(
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,19 @@ const csv = require('csv-parser');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { createObjectCsvWriter } = require('csv-writer');
|
const { createObjectCsvWriter } = require('csv-writer');
|
||||||
|
const storageService = require('../services/storageService');
|
||||||
|
|
||||||
// Configure multer for file uploads
|
// Configure multer for file uploads
|
||||||
const upload = multer({
|
// const upload = multer({
|
||||||
dest: path.join(__dirname, '../uploads/temp'),
|
// dest: path.join(__dirname, '../uploads/temp'),
|
||||||
limits: { fileSize: 10 * 1024 * 1024 } // 10MB limit
|
// limits: { fileSize: 10 * 1024 * 1024 } // 10MB limit
|
||||||
});
|
// });
|
||||||
|
|
||||||
module.exports = (pool, query, authMiddleware) => {
|
module.exports = (pool, query, authMiddleware) => {
|
||||||
// Apply authentication middleware to all routes
|
// Apply authentication middleware to all routes
|
||||||
router.use(authMiddleware);
|
router.use(authMiddleware);
|
||||||
|
|
||||||
|
const upload = storageService.getUploadMiddleware();
|
||||||
/**
|
/**
|
||||||
* Get all mailing lists
|
* Get all mailing lists
|
||||||
* GET /api/admin/mailing-lists
|
* GET /api/admin/mailing-lists
|
||||||
|
|
@ -512,16 +514,45 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
|
|
||||||
// Process the CSV file
|
// Process the CSV file
|
||||||
const results = [];
|
const results = [];
|
||||||
|
// const processFile = () => {
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// fs.createReadStream(file.path)
|
||||||
|
// .pipe(csv())
|
||||||
|
// .on('data', (data) => results.push(data))
|
||||||
|
// .on('end', () => {
|
||||||
|
// // Clean up temp file
|
||||||
|
// fs.unlink(file.path, (err) => {
|
||||||
|
// if (err) console.error('Error deleting temp file:', err);
|
||||||
|
// });
|
||||||
|
// resolve(results);
|
||||||
|
// })
|
||||||
|
// .on('error', reject);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
const processFile = () => {
|
const processFile = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// For S3 uploads, file.path won't exist, but file.location will
|
||||||
|
const filePath = file.path || file.location;
|
||||||
|
|
||||||
|
// If S3 storage, we need to download the file first
|
||||||
|
if (!file.path && file.location) {
|
||||||
|
// Implementation for S3 file would go here
|
||||||
|
// For now, we'll reject as this would need a different approach
|
||||||
|
reject(new Error('S3 file processing not implemented'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local file processing
|
||||||
fs.createReadStream(file.path)
|
fs.createReadStream(file.path)
|
||||||
.pipe(csv())
|
.pipe(csv())
|
||||||
.on('data', (data) => results.push(data))
|
.on('data', (data) => results.push(data))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
// Clean up temp file
|
// Clean up temp file - only for local storage
|
||||||
fs.unlink(file.path, (err) => {
|
if (file.path) {
|
||||||
if (err) console.error('Error deleting temp file:', err);
|
fs.unlink(file.path, (err) => {
|
||||||
});
|
if (err) console.error('Error deleting temp file:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
resolve(results);
|
resolve(results);
|
||||||
})
|
})
|
||||||
.on('error', reject);
|
.on('error', reject);
|
||||||
|
|
@ -683,6 +714,10 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
// Create a temp file for CSV
|
// Create a temp file for CSV
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const fileName = `subscribers-${listName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${timestamp}.csv`;
|
const fileName = `subscribers-${listName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${timestamp}.csv`;
|
||||||
|
const tempDir = path.join(__dirname, '../uploads/temp');
|
||||||
|
if (!fs.existsSync(tempDir)) {
|
||||||
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
|
}
|
||||||
const filePath = path.join(__dirname, '../uploads/temp', fileName);
|
const filePath = path.join(__dirname, '../uploads/temp', fileName);
|
||||||
|
|
||||||
// Create CSV writer
|
// Create CSV writer
|
||||||
|
|
|
||||||
58
backend/src/services/cacheService.js
Normal file
58
backend/src/services/cacheService.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
const Redis = require('ioredis');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
class CacheService {
|
||||||
|
constructor() {
|
||||||
|
this.enabled = config.site.deployment === 'cloud' && config.site.redisHost
|
||||||
|
this.client = null;
|
||||||
|
|
||||||
|
if (this.enabled) {
|
||||||
|
this.client = new Redis({
|
||||||
|
host: config.site.redisHost,
|
||||||
|
port: config.site.redisPort,
|
||||||
|
password: config.site.redisPassword,
|
||||||
|
tls: config.site.redisTLS ? {} : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on('error', (err) => {
|
||||||
|
console.error('Redis Client Error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key) {
|
||||||
|
if (!this.enabled) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const value = await this.client.get(key);
|
||||||
|
return value ? JSON.parse(value) : null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cache get error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key, value, ttlSeconds = 300) {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.client.setex(key, ttlSeconds, JSON.stringify(value));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cache set error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(key) {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.client.del(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cache delete error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a singleton instance
|
||||||
|
const cacheService = new CacheService();
|
||||||
|
module.exports = cacheService;
|
||||||
83
backend/src/services/storageService.js
Normal file
83
backend/src/services/storageService.js
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// services/storageService.js
|
||||||
|
const multer = require('multer');
|
||||||
|
const multerS3 = require('multer-s3');
|
||||||
|
const { S3Client } = require('@aws-sdk/client-s3');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
class StorageService {
|
||||||
|
constructor() {
|
||||||
|
this.mode = config.site.deployment;
|
||||||
|
this.s3Client = null;
|
||||||
|
|
||||||
|
if (this.mode === 'cloud' && config.site.awsS3Bucket) {
|
||||||
|
this.s3Client = new S3Client({ region: config.site.awsRegion });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getUploadMiddleware() {
|
||||||
|
if (this.mode === 'cloud' && config.site.awsS3Bucket) {
|
||||||
|
// Cloud mode: Use S3
|
||||||
|
return multer({
|
||||||
|
storage: multerS3({
|
||||||
|
s3: this.s3Client,
|
||||||
|
bucket: config.site.awsS3Bucket,
|
||||||
|
acl: 'public-read',
|
||||||
|
key: (req, file, cb) => {
|
||||||
|
const folder = req.path.includes('/product') ? 'products' : 'blog';
|
||||||
|
cb(null, `${folder}/${Date.now()}-${file.originalname}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fileFilter: this._fileFilter,
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Self-hosted mode: Use local storage
|
||||||
|
return multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, file, cb) => {
|
||||||
|
const uploadDir = path.join(__dirname, '../../public/uploads');
|
||||||
|
const folder = req.path.includes('/product') ? 'products' : 'blog';
|
||||||
|
const finalPath = path.join(uploadDir, folder);
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
if (!fs.existsSync(finalPath)) {
|
||||||
|
fs.mkdirSync(finalPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, finalPath);
|
||||||
|
},
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
cb(null, `${Date.now()}-${file.originalname}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fileFilter: this._fileFilter,
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fileFilter(req, file, cb) {
|
||||||
|
if (file.mimetype.startsWith('image/')) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Only image files are allowed!'), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getImageUrl(path) {
|
||||||
|
if (!path) return null;
|
||||||
|
|
||||||
|
if (this.mode === 'cloud' && config.site.cdnDomain) {
|
||||||
|
// Use CloudFront CDN in cloud mode
|
||||||
|
return `https://${config.site.cdnDomain}${path}`;
|
||||||
|
} else {
|
||||||
|
// Use direct path in self-hosted mode
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageService = new StorageService();
|
||||||
|
module.exports = storageService;
|
||||||
Loading…
Reference in a new issue