From 8be2634c158c2281ddf9c063b0c677844c6161af Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 14 Feb 2024 20:35:00 +0100 Subject: [PATCH] added logger --- package-lock.json | 200 ++++++++++++++++++++- package.json | 4 +- server.ts | 8 +- src/controllers/calendarController.ts | 24 ++- src/controllers/storeController.ts | 15 ++ src/controllers/storeServicesController.ts | 42 ++++- src/controllers/userController.ts | 52 +++++- src/controllers/usersController.ts | 57 ++++-- src/logger/logger.ts | 135 +++++++++++++- src/validator/validator.ts | 3 + 10 files changed, 514 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea96d4c..b2052f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,9 @@ "sequelize": "^6.35.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "winston": "^3.11.0", + "winston-transport": "^4.7.0" }, "devDependencies": { "@types/amqplib": "^0.10.4", @@ -138,6 +140,24 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -410,6 +430,11 @@ "@types/serve-static": "*" } }, + "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/uuid": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", @@ -560,6 +585,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.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -761,6 +791,15 @@ "node": ">=12" } }, + "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/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -777,6 +816,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -785,11 +833,33 @@ "color-support": "bin.js" } }, + "node_modules/color/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/color/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/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "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/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1041,6 +1111,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", @@ -1232,6 +1307,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "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/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -1249,6 +1329,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/follow-redirects": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", @@ -1591,6 +1676,11 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1599,6 +1689,17 @@ "node": ">=8" } }, + "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": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -1634,6 +1735,11 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1654,6 +1760,27 @@ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "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/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1975,6 +2102,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/openapi-types": { "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", @@ -2561,6 +2696,14 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sonic-boom": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", @@ -2582,6 +2725,14 @@ "node": ">= 10.x" } }, + "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", @@ -2712,6 +2863,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/thread-stream": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", @@ -2746,6 +2902,14 @@ "tree-kill": "cli.js" } }, + "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/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -2885,6 +3049,40 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.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.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", diff --git a/package.json b/package.json index 3820232..5cfec70 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "sequelize": "^6.35.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "winston": "^3.11.0", + "winston-transport": "^4.7.0" }, "devDependencies": { "@types/amqplib": "^0.10.4", diff --git a/server.ts b/server.ts index 0e6c764..a0e7f4a 100644 --- a/server.ts +++ b/server.ts @@ -18,7 +18,7 @@ dotenv.config(); import swaggerJsDoc from "swagger-jsdoc"; import syncModels from "./src/models/index"; -import logger from "./src/logger/logger"; +import logger, { initLogHandler, userLogger } from "./src/logger/logger"; import passport from "passport"; import rabbitmq from "./src/rabbitmq/rabbitmq"; import { GOOGLE_CALLBACK_URL } from "./src/utils/constants"; @@ -120,12 +120,12 @@ const specs = swaggerJsDoc(options); app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs)); app.use((req, res, next) => { - console.log("req not found, path:", req.path); + logger.warn(`reqnot found, path: ${req.path}`); res.status(404).send("not found"); }); app.use((err: any, req: any, res: any, next: any) => { - console.log("req err", err.stack); + logger.error(`req err: ${err.stack}`); res.status(500).send({ err: "invalid request" }); }); @@ -133,6 +133,8 @@ syncModels(); // start server +initLogHandler(); + rabbitmq .connect( process.env.RABBITMQ_USERNAME as string, diff --git a/src/controllers/calendarController.ts b/src/controllers/calendarController.ts index faefee9..bfd21de 100644 --- a/src/controllers/calendarController.ts +++ b/src/controllers/calendarController.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import logger from "../logger/logger"; +import logger, { storeLogger, userLogger } from "../logger/logger"; import Store from "../models/store"; import { decodeBase64, getUserSession, matchPassword } from "../utils/utils"; import User from "../models/user"; @@ -60,6 +60,8 @@ export async function GetCalendarSettings(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } + userLogger.info(userSession.user_id, "Get calendar settings"); + // check if the user has the google calendar connected const googleCalendarConnected = await isTerminPlanerGoogleCalendarConnected( @@ -243,6 +245,16 @@ export async function UpdatePersonalCalendarSettings( } ); + userLogger.info( + userSession.user_id, + "Updated personal calendar settings to using primary calendar:", + calendarUsingPrimaryCalendar, + "max future booking days:", + calendarMaxFutureBookingDays, + "min earliest booking time:", + calendarMinEarliestBookingTime + ); + res.status(200).send({ msg: "calendar settings updated" }); } catch (error) { logger.error(error); @@ -349,6 +361,14 @@ export async function UpdateStoreCalendarSettings(req: Request, res: Response) { }, }); + storeLogger.info( + store.store_id, + "Updated store calendar settings to max future booking days:", + calendarMaxFutureBookingDays, + "min earliest booking time:", + calendarMinEarliestBookingTime + ); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); @@ -434,6 +454,8 @@ export async function UnlinkGoogleCalendar(req: Request, res: Response) { } ); + userLogger.info(userSession.user_id, "Unlinked google calendar"); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); diff --git a/src/controllers/storeController.ts b/src/controllers/storeController.ts index 131816c..979c77f 100644 --- a/src/controllers/storeController.ts +++ b/src/controllers/storeController.ts @@ -2,6 +2,7 @@ import { Request, Response } from "express"; import Store from "../models/store"; import { getUserSession } from "../utils/utils"; import { isCompanyNameValid } from "../validator/validator"; +import { storeLogger, userLogger } from "../logger/logger"; export async function GetStore(req: Request, res: Response) { try { @@ -19,6 +20,8 @@ export async function GetStore(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } + userLogger.info(userSession.user_id, "GetStore"); + const store = await Store.findOne({ where: { store_id: storeId, @@ -95,6 +98,18 @@ export async function UpdateStore(req: Request, res: Response) { } ); + storeLogger.info( + storeId, + "Updated store info to name:", + name, + "phone number:", + phoneNumber, + "email:", + email, + "address:", + address + ); + res.status(200).send({ msg: "success" }); } catch (error) { res.status(500).send({ err: "invalid request" }); diff --git a/src/controllers/storeServicesController.ts b/src/controllers/storeServicesController.ts index 0ccdc6b..c50007b 100644 --- a/src/controllers/storeServicesController.ts +++ b/src/controllers/storeServicesController.ts @@ -13,7 +13,7 @@ import { isStoreServiceNameValid, } from "../validator/validator"; import Store from "../models/store"; -import logger from "../logger/logger"; +import logger, { storeLogger, userLogger } from "../logger/logger"; import StoreServiceActivity from "../models/storeServiceActivity"; import StoreServiceActivityUsers from "../models/storeServiceActivityUsers"; import User from "../models/user"; @@ -44,6 +44,12 @@ export async function GetStoreServices(req: Request, res: Response) { attributes: ["user_id", "username"], }); + const userSession = await getUserSession(req); + + if (userSession) { + userLogger.info(userSession.user_id, "GetStoreServices"); + } + res.status(200).send({ services, users }); } catch (error) { console.log(error); @@ -90,6 +96,8 @@ export async function CreateStoreService(req: Request, res: Response) { name: name, }); + storeLogger.info(storeId, `Created service: ${name}`); + res.status(200).send({ service }); } catch (error) { console.log(error); @@ -151,6 +159,8 @@ export async function UpdateStoreService(req: Request, res: Response) { } ); + storeLogger.info(store.store_id, `Updated service name: ${name}`); + res.status(200).send({ msg: "success" }); } catch (error) { console.log(error); @@ -186,6 +196,7 @@ export async function CreateStoreServiceActivity(req: Request, res: Response) { where: { service_id: serviceId, }, + attributes: ["store_id"], }); if (!service) { @@ -196,6 +207,7 @@ export async function CreateStoreServiceActivity(req: Request, res: Response) { where: { store_id: service.store_id, }, + attributes: ["owner_user_id"], }); if (!store) { @@ -208,8 +220,10 @@ export async function CreateStoreServiceActivity(req: Request, res: Response) { // create store service activity + const activityId = newStoreServiceActivityId(); + const activity = await StoreServiceActivity.create({ - activity_id: newStoreServiceActivityId(), + activity_id: activityId, service_id: serviceId, name: name, description: description, @@ -227,6 +241,11 @@ export async function CreateStoreServiceActivity(req: Request, res: Response) { }); } + storeLogger.info( + service.store_id, + `Created activity id: ${activityId} for service: ${serviceId}. Details: name: ${name} description: ${description} price: ${price} duration: ${duration} users: ${userIds}` + ); + res.status(200).send({ activity }); } catch (error) { console.log(error); @@ -255,6 +274,12 @@ export async function GetStoreServiceActivities(req: Request, res: Response) { ], }); + const userSession = await getUserSession(req); + + if (userSession) { + userLogger.info(userSession.user_id, "GetStoreServiceActivities"); + } + res.status(200).send({ activities: activities }); } catch (error) { console.log(error); @@ -386,6 +411,11 @@ export async function UpdateStoreServiceActivity(req: Request, res: Response) { }); } + storeLogger.info( + service.store_id, + `Updated activity id: ${activityId}. Details: name: ${name} description: ${description} price: ${price} duration: ${duration} users: ${userIds}` + ); + res.status(200).send({ msg: "success" }); } catch (error) { console.log(error); @@ -414,6 +444,7 @@ export async function DeleteStoreServiceActivity(req: Request, res: Response) { where: { activity_id: activityId, }, + attributes: ["service_id"], }); if (!activity) { @@ -424,6 +455,7 @@ export async function DeleteStoreServiceActivity(req: Request, res: Response) { where: { service_id: activity.service_id, }, + attributes: ["store_id"], }); if (!service) { @@ -434,6 +466,7 @@ export async function DeleteStoreServiceActivity(req: Request, res: Response) { where: { store_id: service.store_id, }, + attributes: ["owner_user_id"], }); if (!store) { @@ -460,6 +493,8 @@ export async function DeleteStoreServiceActivity(req: Request, res: Response) { }, }); + storeLogger.info(service.store_id, `Deleted activity id: ${activityId}`); + res.status(200).send({ msg: "success" }); } catch (error) { console.log(error); @@ -498,6 +533,7 @@ export async function DeleteStoreService(req: Request, res: Response) { where: { store_id: service.store_id, }, + attributes: ["owner_user_id"], }); if (!store) { @@ -543,6 +579,8 @@ export async function DeleteStoreService(req: Request, res: Response) { }, }); + storeLogger.info(service.store_id, `Deleted service id: ${serviceId}`); + res.status(200).send({ msg: "success" }); } catch (error) { console.log(error); diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 1512f39..11e6b59 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import logger from "../logger/logger"; +import logger, { userLogger } from "../logger/logger"; import User from "../models/user"; import { isCompanyNameValid, @@ -154,6 +154,10 @@ export async function SignUp(req: Request, res: Response) { payment_plan: PAYMENT_PLAN.DEMO, }); + logger.info( + `new user signed up: user_id: ${userId} email: ${email} language: ${language} company: ${companyName} username: ${username}` + ); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); @@ -265,6 +269,8 @@ export async function Login(req: Request, res: Response) { } ); + userLogger.info(user.user_id, "User logged in"); + // create session saveSession(req, res, user.user_id, user.username, rememberMe); } catch (error) { @@ -332,6 +338,8 @@ export async function ForgotPassword(req: Request, res: Response) { } ); + userLogger.info(user.user_id, "User forgot password"); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); @@ -347,6 +355,8 @@ export async function Logout(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } + userLogger.info(session.user_id, "User logged out"); + await session.destroy(); res.status(200).send({ msg: "logout successful" }); @@ -383,6 +393,8 @@ export async function GetUser(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } + userLogger.info(session.user_id, "GetUser"); + const stores = await Store.findAll({ where: { owner_user_id: user.user_id, @@ -525,6 +537,8 @@ export async function GetUserProfileSettings(req: Request, res: Response) { attributes: ["language", "analytics_enabled", "username", "email"], }); + userLogger.info(session.user_id, "GetUserProfileSettings"); + res.status(200).json(user); } catch (error) { logger.error(error); @@ -574,6 +588,11 @@ export async function UpdateUserProfileSettings(req: Request, res: Response) { await user.save(); + userLogger.info( + session.user_id, + `User updated profile setting to username: ${username} language: ${language} analyticsEnabled: ${analyticsEnabled}` + ); + res.status(200).send({ msg: "user profile settings updated" }); } catch (error) { logger.error(error); @@ -643,6 +662,8 @@ export async function UpdateUserProfilePassword(req: Request, res: Response) { }, }); + userLogger.info(session.user_id, "User updated password"); + res.status(200).send({ msg: "user password updated" }); } catch (error) { logger.error(error); @@ -682,6 +703,8 @@ export async function GetUserProfileSessions(req: Request, res: Response) { }; }); + userLogger.info(session.user_id, "GetUserProfileSessions"); + res.status(200).json({ sessions: sessionsList, currentSession: currentSession, @@ -713,6 +736,8 @@ export async function DeleteUserProfileSession(req: Request, res: Response) { }, }); + userLogger.info(session.user_id, `User deleted session: ${id}`); + res.status(200).send({ msg: "session deleted" }); } catch (error) { logger.error(error); @@ -782,6 +807,8 @@ export async function DeleteUserProfile(req: Request, res: Response) { feedback: `${reason} - ${feedback}`, }); + userLogger.info(session.user_id, "User deleted profile"); + res.status(200).send({ msg: "user deleted" }); } catch (error) { logger.error(error); @@ -860,11 +887,18 @@ export async function ExportUserAccount(req: Request, res: Response) { process.env.ACCOUNT_EXPORT_URL as string }${accountExportId}`, }); + + userLogger.info( + session.user_id, + "User account exported and sent via email" + ); } catch (error) { logger.error(error); } })(); + userLogger.info(session.user_id, "User requested account export"); + res.status(200).json({}); } catch (error) { logger.error(error); @@ -891,6 +925,8 @@ export async function GetExportedUserAccount(req: Request, res: Response) { // delete file after download fs.remove(file); + + logger.info(`User account export downloaded: ${id}`); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -936,6 +972,8 @@ export async function VerifyEmail(req: Request, res: Response) { }, }); + userLogger.info(emailVerification.user_id, "User verified email"); + res.status(200).send({ msg: "email verified" }); return; } else if ( @@ -1015,6 +1053,8 @@ export async function VerifyEmail(req: Request, res: Response) { }, }); + userLogger.info(emailVerification.user_id, "User verified email change"); + res.status(200).send({ msg: "email changed" }); return; } else if ( @@ -1071,6 +1111,11 @@ export async function VerifyEmail(req: Request, res: Response) { }, }); + userLogger.info( + emailVerification.user_id, + "User verified forgot password" + ); + res.status(200).send({ msg: "password changed" }); return; } @@ -1137,6 +1182,11 @@ export async function UpdateUserProfileEmail(req: Request, res: Response) { } ); + userLogger.info( + session.user_id, + `Userupdated email to new email: ${email}` + ); + res.status(200).send({ msg: "user email updated" }); } catch (error) { logger.error(error); diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts index 26ea8b2..e1901c8 100644 --- a/src/controllers/usersController.ts +++ b/src/controllers/usersController.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import logger from "../logger/logger"; +import logger, { storeLogger, userLogger } from "../logger/logger"; import { isEmailValid, isLanguageCodeValid, @@ -189,6 +189,16 @@ export async function AddEmployee(req: Request, res: Response) { userId: userId, }); + storeLogger.info( + storeId, + "Added employee with email:", + email, + "username:", + username, + "passwordSetOnInitLogging:", + passwordSetOnInitLogging + ); + return res.status(200).send({ msg: "success" }); } catch (err) { // remove user from database if terminplaner request failed @@ -198,10 +208,22 @@ export async function AddEmployee(req: Request, res: Response) { }, }); + logger.error("terminPlanerRequest err on add employee:", err); + return res.status(500).send({ err: "invalid request" }); } } + storeLogger.info( + storeId, + "Added employee with email:", + email, + "username:", + username, + "passwordSetOnInitLogging:", + passwordSetOnInitLogging + ); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); @@ -264,6 +286,8 @@ export async function GetEmployees(req: Request, res: Response) { (employee) => employee.user_id !== requesterSession.user_id ); + userLogger.info(requesterSession.user_id, "GetEmployees"); + res.status(200).send({ storeSettings: { calendar_max_future_booking_days: @@ -298,7 +322,6 @@ export async function UpdateEmployee(req: Request, res: Response) { const requesterSession = await getUserSession(req); if (!requesterSession) { - logger.debug("Requester session not found"); return res.status(401).send({ err: "unauthorized" }); } @@ -306,10 +329,10 @@ export async function UpdateEmployee(req: Request, res: Response) { where: { owner_user_id: requesterSession.user_id, }, + attributes: ["store_id"], }); if (!store) { - logger.debug("Store not found"); return res.status(400).send({ err: "invalid request" }); } @@ -322,7 +345,6 @@ export async function UpdateEmployee(req: Request, res: Response) { }); if (!existingUser) { - logger.debug("User not found"); return res.status(400).send({ err: "invalid request" }); } @@ -382,14 +404,19 @@ export async function UpdateEmployee(req: Request, res: Response) { where: { user_id: userId, }, - }) - .then(() => { - res.status(200).send({ msg: "success" }); - }) - .catch((err) => { - logger.error(err); - res.status(500).send({ err: "invalid request" }); - }); + }); + + storeLogger.info( + store.store_id, + "Updated employee with user id:", + userId, + "username:", + username, + "email:", + email + ); + + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -411,7 +438,6 @@ export async function DeleteEmployee(req: Request, res: Response) { const requesterSession = await getUserSession(req); if (!requesterSession) { - logger.debug("Requester session not found"); return res.status(401).send({ err: "unauthorized" }); } @@ -419,10 +445,10 @@ export async function DeleteEmployee(req: Request, res: Response) { where: { owner_user_id: requesterSession.user_id, }, + attributes: ["store_id", "owner_user_id"], }); if (!store) { - logger.debug("Store not found"); return res.status(400).send({ err: "invalid request" }); } @@ -435,7 +461,6 @@ export async function DeleteEmployee(req: Request, res: Response) { }); if (!existingUser) { - logger.debug("User not found"); return res.status(400).send({ err: "invalid request" }); } @@ -456,6 +481,8 @@ export async function DeleteEmployee(req: Request, res: Response) { }, }); + storeLogger.info(store.store_id, "Deleted employee with user id:", userId); + res.status(200).send({ msg: "success" }); } catch (error) { logger.error(error); diff --git a/src/logger/logger.ts b/src/logger/logger.ts index 40ee938..d17ac0b 100644 --- a/src/logger/logger.ts +++ b/src/logger/logger.ts @@ -1,5 +1,136 @@ -import pino from "pino"; +import winston from "winston"; +import Transport from "winston-transport"; -const logger = pino({ level: "debug" }); +let cachedLogs = new Map(); + +export async function initLogHandler() { + while (true) { + if (cachedLogs.size > 0) { + for (let [logType, logs] of cachedLogs) { + const url = process.env.LOG_MANAGER_URL!; + const defaultLogType = process.env.NODE_APP_NAME!; + const body = JSON.stringify({ + type: logType === undefined ? defaultLogType : logType, + logs: logs, + }); + + try { + const response = await fetch(url, { + method: "POST", + body: body, + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + cachedLogs.delete(logType); + } catch (err) { + console.warn(err); + } + } + } + + await new Promise((resolve) => + setTimeout(resolve, Number(process.env.LOG_MANAGER_INTERVAL)) + ); + } +} + +class MyTransport extends Transport { + log(info: any, callback: any) { + setImmediate(() => this.emit("logged", info)); + + const currentDate = new Date(); + const formattedDate = `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}:${currentDate.getMilliseconds()}`; + + let logLevel; + + switch (info["level"]) { + case "info": + logLevel = "I"; + break; + case "error": + logLevel = "E"; + break; + default: + logLevel = "W"; + } + + const msg = `${logLevel} ${formattedDate} ${info["message"]}`; + + if (cachedLogs.has(info["logType"])) { + cachedLogs.get(info["logType"]).push(msg); + + // for testing purposes send all logs to NODE_APP_NAME + if (info["logType"] !== process.env.NODE_APP_NAME!) { + cachedLogs.get(process.env.NODE_APP_NAME!).push(msg); + } + } else { + cachedLogs.set(info["logType"], [msg]); + + // for testing purposes send all logs to NODE_APP_NAME + if (info["logType"] !== process.env.NODE_APP_NAME!) { + cachedLogs.set(process.env.NODE_APP_NAME!, [msg]); + } + } + + callback(); + } +} + +const logger = winston.createLogger({ + level: "debug", + format: winston.format.json(), + transports: [new MyTransport()], +}); + +if (process.env.NODE_ENV !== "production") { + logger.add( + new winston.transports.Console({ + format: winston.format.simple(), + }) + ); +} + +class BaseLogger { + constructor(private logTypePrefix: string) {} + + info(id: string, ...messages: string[]) { + this.log("info", id, ...messages); + } + + error(id: string, ...messages: string[]) { + this.log("error", id, ...messages); + } + + warn(id: string, ...messages: string[]) { + this.log("warn", id, ...messages); + } + + private log(level: string, id: string, ...messages: string[]) { + logger.log({ + level: level, + logType: `${this.logTypePrefix}-${id}`, + message: messages.join(" "), + }); + } +} + +class UserLogger extends BaseLogger { + constructor() { + super("user"); + } +} + +class StoreLogger extends BaseLogger { + constructor() { + super("store"); + } +} + +export const userLogger = new UserLogger(); +export const storeLogger = new StoreLogger(); export default logger; diff --git a/src/validator/validator.ts b/src/validator/validator.ts index d2cb344..85a9845 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -27,6 +27,7 @@ import { } from "../utils/constants"; import User from "../models/user"; import UserPendingEmailChange from "../models/userPendingEmailChange"; +import logger from "../logger/logger"; // TODO: regex for username export function isUsernameValid(username: string) { @@ -40,6 +41,8 @@ export async function isEmailValid( email: string, checkDatabase: boolean = true ) { + logger.info(`isEmailValid: ${email}`); + if ( email.length < EMAIL_MIN_LENGTH || email.length > EMAIL_MAX_LENGTH ||