diff --git a/backend/.gitignore b/backend/.gitignore index 37d7e73..3c65cad 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,66 @@ -node_modules +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +/log + +# Environment variables .env +.env.local +.env.*.local + +# Prisma +prisma/.env +prisma/migrations/ + +# Build +build/ +dist/ +out/ +.cache/ + +# Static files +public/static/ + +# Winston log files +logs/ +*.log + +# VS Code +.vscode/ + +# MacOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db + +# JetBrains IDEs +.idea/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Parcel-bundler cache +.cache/ + +# Typescript +*.tsbuildinfo + +# Jest +/coverage diff --git a/backend/package-lock.json b/backend/package-lock.json index 05cdde4..b1f304e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -26,7 +26,9 @@ "multer-storage-cloudinary": "^4.0.0", "mysql2": "^3.11.0", "prisma": "^5.19.1", - "sharp": "^0.33.5" + "sharp": "^0.33.5", + "winston": "^3.15.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@types/express": "^4.17.21", @@ -36,6 +38,14 @@ "typescript": "^5.5.4" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -48,6 +58,16 @@ "node": ">=12" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -825,6 +845,11 @@ "@types/send": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -838,6 +863,17 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -961,6 +997,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, "node_modules/async-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", @@ -982,6 +1023,25 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -1051,6 +1111,29 @@ "node": ">=8" } }, + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1192,6 +1275,37 @@ "color-support": "bin.js" } }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1408,6 +1522,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1448,6 +1567,22 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -1515,6 +1650,11 @@ "node": ">= 8.0.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -1537,6 +1677,14 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1566,6 +1714,11 @@ "node": ">= 0.8" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1847,6 +2000,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1936,6 +2108,17 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1946,6 +2129,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/libsql": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.3.19.tgz", @@ -1980,6 +2168,27 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/logform": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -2133,6 +2342,14 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2365,6 +2582,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -2395,6 +2620,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2446,6 +2679,14 @@ "fsevents": "2.3.3" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2579,6 +2820,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2764,6 +3013,14 @@ "node": ">= 0.6" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2840,6 +3097,11 @@ "node": ">=10" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2874,6 +3136,14 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -3036,6 +3306,72 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", + "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.6.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.8.0.tgz", + "integrity": "sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==", + "dependencies": { + "logform": "^2.6.1", + "readable-stream": "^4.5.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/backend/package.json b/backend/package.json index 102a6fb..906104f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -38,6 +38,8 @@ "multer-storage-cloudinary": "^4.0.0", "mysql2": "^3.11.0", "prisma": "^5.19.1", - "sharp": "^0.33.5" + "sharp": "^0.33.5", + "winston": "^3.15.0", + "winston-daily-rotate-file": "^5.0.0" } } diff --git a/backend/src/config/logger.ts b/backend/src/config/logger.ts new file mode 100644 index 0000000..260d090 --- /dev/null +++ b/backend/src/config/logger.ts @@ -0,0 +1,27 @@ +import { createLogger, format, transports } from 'winston'; +import DailyRotateFile from 'winston-daily-rotate-file'; + +const { combine, timestamp, printf } = format; + +const logFormat = printf(({ level, message, timestamp }) => { + return `${timestamp} [${level}]: ${message}`; +}); + +const logger = createLogger({ + format: combine( + timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + logFormat + ), + transports: [ + new transports.Console(), // Logs to the console + new DailyRotateFile({ + filename: 'logs/application-%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxFiles: '14d', // Keeps logs for 14 days + }), + ], +}); + +export default logger; diff --git a/backend/src/controllers/club.controller.ts b/backend/src/controllers/club.controller.ts index e61105f..feb6f2e 100644 --- a/backend/src/controllers/club.controller.ts +++ b/backend/src/controllers/club.controller.ts @@ -3,7 +3,7 @@ import { prisma } from "../config/database.config"; import bcrypt from "bcrypt"; import { MESSAGES } from "../config/const"; import { uploadClubLogo, uploadImageToCloudinary, uploads } from "../services/cloudnary"; - +import logger from "../config/logger" export const getClubData = async (req: Request, res: Response) => { try { const data = await prisma.club.findMany({ @@ -14,25 +14,24 @@ export const getClubData = async (req: Request, res: Response) => { ClubImages: true, }, }); + logger.info("Fetched club data successfully."); res.json(data); } catch (error) { - console.error(MESSAGES.CLUB.ERROR_FETCHING_DATA, error); + logger.error(`${MESSAGES.CLUB.ERROR_FETCHING_DATA}: ${error}`); res.status(500).json({ error: MESSAGES.CLUB.ERROR_FETCHING_DATA }); } }; export const addClub = async (req: Request, res: Response) => { try { - const { ClubName, Description, FoundedDate, Email, Password, LogoURL } = - req.body; + const { ClubName, Description, FoundedDate, Email, Password, LogoURL } = req.body; if (!ClubName || !Email || !Password) { - return res - .status(400) - .json({ error: MESSAGES.CLUB.CLUB_NAME_EMAIL_PASSWORD_REQUIRED }); + logger.warn("Attempted to add a club with missing required fields."); + return res.status(400).json({ error: MESSAGES.CLUB.CLUB_NAME_EMAIL_PASSWORD_REQUIRED }); } - const hashedPassword = await bcrypt.hash(Password, 10); + const hashedPassword = await bcrypt.hash(Password, 10); const newClub = await prisma.club.create({ data: { ClubName, @@ -40,13 +39,14 @@ export const addClub = async (req: Request, res: Response) => { FoundedDate, Email, Password: hashedPassword, - LogoURL:'https://img.icons8.com/ios-filled/50/test-account.png', + LogoURL: 'https://img.icons8.com/ios-filled/50/test-account.png', }, }); + logger.info(`New club added: ${ClubName}`); res.status(201).json(newClub); } catch (error) { - console.error(MESSAGES.CLUB.ERROR_CREATING_CLUB, error); + logger.error(`${MESSAGES.CLUB.ERROR_CREATING_CLUB}: ${error}`); res.status(500).json({ error: MESSAGES.CLUB.ERROR_CREATING_CLUB }); } }; @@ -54,6 +54,7 @@ export const addClub = async (req: Request, res: Response) => { export const login = async (req: Request, res: Response) => { const { email, password } = req.body; if (!email || !password) { + logger.warn("Login attempt with missing email or password."); return res.status(400).json({ error: MESSAGES.CLUB.CLUB_NAME_EMAIL_PASSWORD_REQUIRED }); } try { @@ -61,21 +62,22 @@ export const login = async (req: Request, res: Response) => { where: { Email: email }, }); if (!club) { - console.log(MESSAGES.CLUB.CLUB_DATA_NOT_FOUND); + logger.warn(`Login attempt with non-existing email: ${email}`); return res.status(404).json({ error: MESSAGES.CLUB.CLUB_DATA_NOT_FOUND }); } + const passwordMatch = await bcrypt.compare(password, club.Password); if (!passwordMatch) { - console.log(`MESSAGES.CLUB.INVALID_EMAIL_OR_PASSWORD for ${email}`) + logger.warn(`Invalid login attempt for email: ${email}`); return res.status(401).json({ error: MESSAGES.CLUB.INVALID_EMAIL_OR_PASSWORD }); - } - console.log(`Login successful for email: ${email}`); + + logger.info(`Login successful for email: ${email}`); club.Password = "encrypted"; res.status(200).json({ message: MESSAGES.CLUB.LOGIN_SUCCESSFUL, club }); } catch (error) { - console.error(MESSAGES.CLUB.LOGIN_ERROR, error); - res.status(500).json({ error: MESSAGES.CLUB.LOGIN_ERROR}); + logger.error(`${MESSAGES.CLUB.LOGIN_ERROR}: ${error}`); + res.status(500).json({ error: MESSAGES.CLUB.LOGIN_ERROR }); } }; @@ -83,43 +85,20 @@ export const getClubMembers = async (req: Request, res: Response) => { try { const clubId = Number(req.query.ClubID); if (isNaN(clubId)) { + logger.warn("Invalid ClubID provided in getClubMembers."); return res.status(400).json({ error: "ClubID is required." }); } + const clubMembers = await prisma.club .findUnique({ where: { ClubID: clubId }, }) .Members(); + + logger.info(`Fetched members for ClubID: ${clubId}`); res.json(clubMembers); } catch (error) { - console.error("Error fetching club members:", error); - res - .status(500) - .json({ error: "An error occurred while fetching club members." }); + logger.error(`Error fetching club members: ${error}`); + res.status(500).json({ error: "An error occurred while fetching club members." }); } -}; - -// export const addClubMember = async (req: Request, res: Response) => { -// try { -// const { ClubID, MemberID } = req.body; -// if (!ClubID || !MemberID) { -// return res -// .status(400) -// .json({ error: "ClubID and MemberID are required." }); -// } -// const newClubMember = await prisma.clubMember.create({ -// data: { -// MemberID, -// ClubID, -// }, -// }); -// res.status(201).json; -// } catch (error) { -// console.error("Error adding club member:", error); -// res -// .status(500) -// .json({ error: "An error occurred while adding club member." }); -// } -// }; - -// Add other club-related controller functions here +}; \ No newline at end of file diff --git a/backend/src/controllers/event.controller.ts b/backend/src/controllers/event.controller.ts index ca9182b..f4d70ce 100644 --- a/backend/src/controllers/event.controller.ts +++ b/backend/src/controllers/event.controller.ts @@ -1,7 +1,8 @@ import { Request, Response } from "express"; import { prisma } from "../config/database.config"; import { MESSAGES } from "../config/const"; - +import logger from "../config/logger"; +import bcrypt from 'bcrypt' // Add other club-related controller functions here export const deleteEvent = async (req: Request, res: Response) => { const { eventId } = req.body; @@ -13,10 +14,12 @@ export const deleteEvent = async (req: Request, res: Response) => { }); if (!event) { + logger.warn(`Event not found for EventID: ${eventId}`); return res.status(404).json({ error: MESSAGES.EVENT.EVENT_NOT_FOUND }); } if (event.ClubID !== clubId) { + logger.warn(`Unauthorized attempt to delete event: ${eventId} by ClubID: ${clubId}`); return res.status(403).json({ error: MESSAGES.EVENT.NOT_AUTHORIZED }); } @@ -24,9 +27,10 @@ export const deleteEvent = async (req: Request, res: Response) => { where: { EventID: eventId }, }); + logger.info(`Event deleted: ${eventId}`); res.status(204).end(); } catch (error) { - console.error(MESSAGES.EVENT.ERROR_DELETING_EVENT, error); + logger.error(`${MESSAGES.EVENT.ERROR_DELETING_EVENT}: ${error}`); res.status(500).json({ error: MESSAGES.EVENT.ERROR_DELETING_EVENT }); } }; @@ -37,9 +41,10 @@ export const getClubEventData = async (req: Request, res: Response) => { const events = await prisma.event.findMany({ where: { ClubID: clubId }, }); + logger.info(`Fetched events for ClubID: ${clubId}`); res.json(events); } catch (error) { - console.error(MESSAGES.EVENT.ERROR_FETCHING_EVENTS, error); + logger.error(`${MESSAGES.EVENT.ERROR_FETCHING_EVENTS}: ${error}`); res.status(500).json({ error: MESSAGES.EVENT.ERROR_FETCHING_EVENTS }); } }; @@ -65,16 +70,16 @@ export const updateEvent = async (req: Request, res: Response) => { Location, }, }); + logger.info(`Event updated: ${EventID} by ClubID: ${clubId}`); res.json(updatedEvent); } catch (error) { - console.error(MESSAGES.EVENT.ERROR_UPDATING_EVENT, error); + logger.error(`${MESSAGES.EVENT.ERROR_UPDATING_EVENT}: ${error}`); res.status(500).json({ error: MESSAGES.EVENT.ERROR_UPDATING_EVENT }); } }; export const createEvent = async (req: Request, res: Response) => { - const { EventName, Description, StartDateTime, EndDateTime, Location } = - req.body; + const { EventName, Description, StartDateTime, EndDateTime, Location } = req.body; const clubId = Number(req.query.ClubID); try { @@ -88,22 +93,60 @@ export const createEvent = async (req: Request, res: Response) => { ClubID: clubId, }, }); + logger.info(`New event created: ${newEvent.EventName} for ClubID: ${clubId}`); res.status(201).json(newEvent); } catch (error) { - console.error(MESSAGES.EVENT.ERROR_CREATING_EVENT, error); + logger.error(`${MESSAGES.EVENT.ERROR_CREATING_EVENT}: ${error}`); res.status(500).json({ error: MESSAGES.EVENT.ERROR_CREATING_EVENT, details: error, - }); + }); } }; export const getAllEventData = async (req: Request, res: Response) => { try { - const clubs = await prisma.event.findMany(); // Fetch all clubs from the database + const clubs = await prisma.event.findMany(); + logger.info("Fetched all event data."); res.json(clubs); } catch (error) { - console.error(MESSAGES.EVENT.ERROR_FETCHING_EVENTS, error); + logger.error(`${MESSAGES.EVENT.ERROR_FETCHING_EVENTS}: ${error}`); res.status(500).json({ error: MESSAGES.EVENT.ERROR_FETCHING_EVENTS }); } }; + +export const createQuiz = async (req: Request, res: Response) => { + const { title, questions, secretCode } = req.body; + const clubId = Number(req.query.ClubID); + if (!secretCode || typeof secretCode !== "string") { + logger.warn("Invalid secret code provided."); + return res.status(400).json({ error: MESSAGES.QUIZ.INVALID_SECRET_CODE }); + } + try { + const hashedSecretCode = await bcrypt.hash(secretCode, 10); + const quiz = await prisma.quiz.create({ + data: { + title, + secretCode: hashedSecretCode, + clubId: clubId, + }, + }); + await Promise.all( + questions.map((q: any) => + prisma.question.create({ + data: { + question: q.question, + options: q.options, + correctAnswer: q.correctAnswer, + quizId: quiz.id, + }, + }), + ), + ); + logger.info(`Quiz created: ${title} for ClubID: ${clubId}`); + res.status(201).json({ quizId: quiz.id }); + } catch (error) { + logger.error(`${MESSAGES.QUIZ.ERROR_CREATING_QUIZ}: ${error}`); + res.status(500).json({ error: MESSAGES.QUIZ.ERROR_CREATING_QUIZ }); + } +}; diff --git a/backend/src/controllers/quiz.controller.ts b/backend/src/controllers/quiz.controller.ts index b259233..4c19ca0 100644 --- a/backend/src/controllers/quiz.controller.ts +++ b/backend/src/controllers/quiz.controller.ts @@ -2,13 +2,17 @@ import { Request, Response } from "express"; import { prisma } from "../config/database.config"; import bcrypt from "bcrypt"; import { MESSAGES } from "../config/const"; +import logger from "../config/logger"; // Import Winston logger export const createQuiz = async (req: Request, res: Response) => { const { title, questions, secretCode } = req.body; const clubId = Number(req.query.ClubID); + if (!secretCode || typeof secretCode !== "string") { + logger.warn("Invalid or missing secret code during quiz creation."); return res.status(400).json({ error: MESSAGES.QUIZ.INVALID_SECRET_CODE }); } + try { const hashedSecretCode = await bcrypt.hash(secretCode, 10); const quiz = await prisma.quiz.create({ @@ -18,6 +22,7 @@ export const createQuiz = async (req: Request, res: Response) => { clubId: clubId, }, }); + await Promise.all( questions.map((q: any) => prisma.question.create({ @@ -27,26 +32,31 @@ export const createQuiz = async (req: Request, res: Response) => { correctAnswer: q.correctAnswer, quizId: quiz.id, }, - }), - ), + }) + ) ); + + logger.info(`Quiz created successfully: ${title} for ClubID: ${clubId}`); res.status(201).json({ quizId: quiz.id }); } catch (error) { - console.error(MESSAGES.QUIZ.ERROR_CREATING_QUIZ, error); + logger.error(`${MESSAGES.QUIZ.ERROR_CREATING_QUIZ}: ${error}`); res.status(500).json({ error: MESSAGES.QUIZ.ERROR_CREATING_QUIZ }); } }; export const getClubQuizzes = async (req: Request, res: Response) => { const clubId = Number(req.query.ClubID); + try { const quizzes = await prisma.quiz.findMany({ where: { clubId: clubId }, select: { id: true, title: true, createdAt: true }, }); + + logger.info(`Fetched quizzes for ClubID: ${clubId}`); res.json(quizzes); } catch (error) { - console.error(MESSAGES.QUIZ.ERROR_FETCHING_QUIZZES, error); + logger.error(`${MESSAGES.QUIZ.ERROR_FETCHING_QUIZZES}: ${error}`); res.status(500).json({ error: MESSAGES.QUIZ.ERROR_FETCHING_QUIZZES }); } }; @@ -55,21 +65,29 @@ export const getClubQuizzes = async (req: Request, res: Response) => { export const getQuizById = async (req: Request, res: Response) => { const { id } = req.params; const { secretCode } = req.body; + try { const quiz = await prisma.quiz.findUnique({ where: { id: parseInt(id) }, include: { questions: true }, }); + if (!quiz) { + logger.warn(`Quiz not found for ID: ${id}`); return res.status(404).json({ error: MESSAGES.QUIZ.QUIZ_NOT_FOUND }); } + const isSecretCodeValid = await bcrypt.compare(secretCode, quiz.secretCode); + if (!isSecretCodeValid) { + logger.warn(`Invalid secret code for quiz ID: ${id}`); return res.status(403).json({ error: MESSAGES.QUIZ.INVALID_SECRET_CODE }); } + + logger.info(`Fetched quiz by ID: ${id}`); res.json(quiz); } catch (error) { - console.error(MESSAGES.QUIZ.ERROR_FETCHING_QUIZ, error); + logger.error(`${MESSAGES.QUIZ.ERROR_FETCHING_QUIZ}: ${error}`); res.status(500).json({ error: MESSAGES.QUIZ.ERROR_FETCHING_QUIZ }); } }; @@ -78,13 +96,16 @@ export const getQuizById = async (req: Request, res: Response) => { export const createQuizUser = async (req: Request, res: Response) => { const { name, rollNo, year } = req.body; const parsedYear = parseInt(year); + try { const user = await prisma.user.create({ data: { name, rollNo, year: parsedYear }, }); + + logger.info(`Created quiz user: ${name}`); res.status(201).json(user); } catch (error) { - console.error(MESSAGES.USER.ERROR_CREATING_USER, error); + logger.error(`${MESSAGES.USER.ERROR_CREATING_USER}: ${error}`); res.status(500).json({ error: MESSAGES.USER.ERROR_CREATING_USER }); } }; @@ -93,6 +114,7 @@ export const createQuizUser = async (req: Request, res: Response) => { export const submitQuiz = async (req: Request, res: Response) => { const { id } = req.params; const { userId, answers, score } = req.body; + try { const result = await prisma.result.create({ data: { @@ -102,9 +124,11 @@ export const submitQuiz = async (req: Request, res: Response) => { user: { connect: { id: userId } }, }, }); + + logger.info(`Quiz result submitted for quiz ID: ${id} by user ID: ${userId}`); res.status(201).json(result); } catch (error) { - console.error(MESSAGES.QUIZ.ERROR_SUBMITTING_QUIZ_RESULT, error); + logger.error(`${MESSAGES.QUIZ.ERROR_SUBMITTING_QUIZ_RESULT}: ${error}`); res.status(500).json({ error: MESSAGES.QUIZ.ERROR_SUBMITTING_QUIZ_RESULT }); } }; @@ -113,21 +137,27 @@ export const submitQuiz = async (req: Request, res: Response) => { export const getQuizResults = async (req: Request, res: Response) => { const { id } = req.params; const clubId = Number(req.query.ClubID); + try { const quiz = await prisma.quiz.findUnique({ where: { id: parseInt(id) }, include: { club: true }, }); + if (!quiz || quiz.clubId !== clubId) { + logger.warn(`Unauthorized access to quiz results for quiz ID: ${id} by ClubID: ${clubId}`); return res.status(403).json({ error: MESSAGES.QUIZ.ACCESS_DENIED }); } + const results = await prisma.result.findMany({ where: { quizId: parseInt(id) }, include: { user: true }, }); + + logger.info(`Fetched quiz results for quiz ID: ${id}`); res.json(results); } catch (error) { - console.error(MESSAGES.QUIZ.ERROR_FETCHING_QUIZ_RESULTS, error); + logger.error(`${MESSAGES.QUIZ.ERROR_FETCHING_QUIZ_RESULTS}: ${error}`); res.status(500).json({ error: MESSAGES.QUIZ.ERROR_FETCHING_QUIZ_RESULTS }); } };