From 17cc263a292a771d83a75ca97d202f3b1889973c Mon Sep 17 00:00:00 2001 From: Awosise Oluwaseun Date: Thu, 29 Jun 2023 17:15:40 +0100 Subject: [PATCH 1/9] fix: get expiresAt time for expired token --- src/accounts/accountsService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accounts/accountsService.js b/src/accounts/accountsService.js index 5579817..1329b8d 100644 --- a/src/accounts/accountsService.js +++ b/src/accounts/accountsService.js @@ -88,7 +88,7 @@ const resetPassword = async ({ token, newPassword }) => { if (!userToken.token === token) throw new BadRequestError('Invalid token'); - const isExpired = userToken.expiryTime.getTime() <= Date.now(); + const isExpired = userToken.expiresAt.getTime() <= Date.now(); if (isExpired) { TokenService.deleteToken(userToken._id); throw new BadRequestError('Expired token'); From 9244b566451a1b0c580bc58d3a9974770b18407e Mon Sep 17 00:00:00 2001 From: Awosise Oluwaseun Date: Thu, 29 Jun 2023 17:47:07 +0100 Subject: [PATCH 2/9] docs: updated docs for forgot and reset password --- src/docs/endpoints.yaml | 158 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 2 deletions(-) diff --git a/src/docs/endpoints.yaml b/src/docs/endpoints.yaml index b650167..dcac716 100644 --- a/src/docs/endpoints.yaml +++ b/src/docs/endpoints.yaml @@ -366,8 +366,8 @@ paths: - Accounts security: - bearer: [] - summary: Post a new question - description: This posts a new question + summary: Change password + description: This changes old password to a new one requestBody: content: application/json: @@ -397,6 +397,73 @@ paths: schema: $ref: '#/components/schemas/ValidationError' + /accounts/forgot-password: + post: + tags: + - Accounts + security: + - bearer: [] + summary: Request OTP to change password + description: This sends OTP to your mail for change of password + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/forgotPassword' + + responses: + '200': + description: Returns request successful + content: + application/json: + schema: + $ref: '#/components/schemas/forgotPasswordResponse' + + '400': + description: 'User does not exist' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + + '500': + description: Email containing OTP was not sent + content: + application/json: + schema: + $ref: '#/components/schemas/MailServerError' + + /accounts/reset-password: + post: + tags: + - Accounts + security: + - bearer: [] + summary: Reset password using OTP + description: This resets your password using OTP + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/resetPassword' + + responses: + '200': + description: Returns the user + content: + application/json: + schema: + $ref: '#/components/schemas/resetPasswordResponse' + + '400': + description: An invalid token or the token has expired + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + delete: tags: - Accounts @@ -2340,6 +2407,32 @@ components: password: newTestPassword1$ retypePassword: newTestPassword1$ + forgotPassword: + title: Request OTP for password reset + required: + - email + type: object + properties: + email: + type: string + example: + email: test@example.com + + resetPassword: + title: Reset password using OTP + required: + - token + - password + type: object + properties: + token: + type: string + password: + type: string + example: + token: 'A four digit token' + password: Otgj7346_h!& + ValidationError: type: object properties: @@ -2355,6 +2448,21 @@ components: message: One or more of user input not validated error: Unprocessable Entity + MailServerError: + type: object + properties: + messsage: + type: string + statusCode: + type: number + default: 500 + error: + type: string + example: + statusCode: 500 + message: connect ECONNREFUSED host:port + error: Something went wrong + BadRequestError: type: object properties: @@ -2968,6 +3076,52 @@ components: type: string __v: type: number + + forgotPasswordResponse: + type: object + properties: + statusCode: + type: number + default: 200 + message: + type: string + + resetPasswordResponse: + type: object + properties: + statusCode: + type: number + default: 200 + message: + type: string + data: + type: object + properties: + isDeleted: + type: boolean + _id: + type: string + firstname: + type: string + lastname: + type: string + email: + type: string + accountType: + type: string + owner: + type: string + createdAt: + type: string + updatedAt: + type: string + bio: + type: string + profilePicture: + type: string + __v: + type: number + # ---------------------------------------------------- consolidated: example: From 129036f37deb6d2f9825eaeb630e6031dc128dbd Mon Sep 17 00:00:00 2001 From: Igbasan Olasunkanmi <86102079+Olatisunkanmi@users.noreply.github.com> Date: Sun, 2 Jul 2023 23:51:41 +0100 Subject: [PATCH 3/9] feature: logger: a logger class that automatically logs all requests and error to a folder --- .gitignore | 2 + app.js | 77 +++++---- bin/www | 8 +- config/index.js | 2 + constant/index.js | 4 + model/post.js | 4 +- model/question.js | 3 +- package-lock.json | 409 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 + utils/db.js | 7 +- utils/helper.js | 156 ++++++++++++------ utils/index.js | 7 + utils/logger.js | 130 +++++++++++++-- 13 files changed, 686 insertions(+), 125 deletions(-) create mode 100644 utils/index.js diff --git a/.gitignore b/.gitignore index ba920d3..a2dd3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules .env coverage +logs/ + diff --git a/app.js b/app.js index 490be1c..5c329bb 100644 --- a/app.js +++ b/app.js @@ -2,48 +2,53 @@ const express = require('express'); const Sentry = require('@sentry/node'); const initializeSentry = require('./middleware/sentry'); const cookieParser = require('cookie-parser'); -const logger = require('morgan'); +const morgan = require('morgan'); +const { Logger } = require('./utils'); const { errorHandler } = require('./utils/errorHandler'); const cors = require('cors'); require('express-async-errors'); -const { CLIENT_URLS } = require('./constant'); - +const { CLIENT_URLS, STUDY_BUDDY } = require('./constant'); const indexRouter = require('./routes/index'); - const app = express(); initializeSentry(); -app.use(Sentry.Handlers.requestHandler()); -app.use(Sentry.Handlers.tracingHandler()); - -app.use( - cors({ - origin: CLIENT_URLS, - }) -); -app.use(logger('dev')); -app.use(express.json({ limit: '50mb' })); -app.use(express.urlencoded({ limit: '50mb', extended: false })); -app.use(cookieParser()); - -app.get('/', (req, res) => { - res.send('Lets make magic! 🚀'); -}); - -app.use('/', indexRouter); - -app.use( - Sentry.Handlers.errorHandler({ - shouldHandleError(error) { - // Capture all 404 and 500 errors - if (error.statusCode === 404 || error.statusCode === 500) { - return true; - } - return false; - }, - }) -); - -app.use(errorHandler); + +global.logger = Logger.createLogger({ label: STUDY_BUDDY }); + +const appConfig = (app) => { + app.use(Sentry.Handlers.requestHandler()); + app.use(Sentry.Handlers.tracingHandler()); + + app.use( + cors({ + origin: CLIENT_URLS, + }) + ); + app.use(morgan('combined', { stream: logger.stream })); + app.use(express.json({ limit: '50mb' })); + app.use(express.urlencoded({ limit: '50mb', extended: false })); + app.use(cookieParser()); + + app.get('/', (req, res) => { + res.send('Lets make magic! 🚀'); + }); + app.use('/', indexRouter); + app.use( + Sentry.Handlers.errorHandler({ + shouldHandleError(error) { + // Capture all 404 and 500 errors + if (error.statusCode === 404 || error.statusCode === 500) { + return true; + } + return false; + }, + }) + ); + + app.use(errorHandler); +}; + +//Initialize app +appConfig(app); module.exports = app; diff --git a/bin/www b/bin/www index 8a82bee..67b05cf 100755 --- a/bin/www +++ b/bin/www @@ -61,9 +61,7 @@ function onError(error) { throw error; } - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; + var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { @@ -87,8 +85,6 @@ function onError(error) { async function onListening() { connectDatabase(config.db.name); var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; + var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); } diff --git a/config/index.js b/config/index.js index c1462bc..dfd2dd5 100644 --- a/config/index.js +++ b/config/index.js @@ -1,6 +1,8 @@ require('dotenv').config(); +const root = require('app-root-path'); module.exports = { + root_path: root.path, app: { port: process.env.PORT || 8000, name: process.env.NAME, diff --git a/constant/index.js b/constant/index.js index 4fa4af8..0eaeb3a 100644 --- a/constant/index.js +++ b/constant/index.js @@ -2,6 +2,10 @@ module.exports = { DEVELOPMENT: 'development', PRODUCTION: 'production', + STUDY_BUDDY: 'Study Buddy', + DB_CONNECTED: 'Database connected successfully to', + ERR_DB_CONNECTION: 'Database connection failed', + ACCOUNT_TYPES: { STUDENT: 'Student', MENTOR: 'Mentor', diff --git a/model/post.js b/model/post.js index f769d62..74dee0c 100644 --- a/model/post.js +++ b/model/post.js @@ -1,5 +1,7 @@ const mongoose = require('mongoose'); -const { sanitiseHTML } = require('../utils/helper'); +const { Helper } = require('../utils'); + +const { sanitiseHTML } = Helper; const postSchema = new mongoose.Schema( { diff --git a/model/question.js b/model/question.js index 19bdf52..05e108b 100644 --- a/model/question.js +++ b/model/question.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose'); -const { generateSlug, sanitiseHTML } = require('../utils/helper'); +const { Helper } = require('../utils'); +const { generateSlug, sanitiseHTML } = Helper; const questionSchema = new mongoose.Schema( { diff --git a/package-lock.json b/package-lock.json index 6424e3a..6f39987 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@sentry/node": "^7.54.0", + "app-root-path": "^3.1.0", "bcrypt": "^5.1.0", "cloudinary": "^1.37.0", "cookie-parser": "~1.4.4", @@ -37,6 +38,7 @@ "passport-local": "^1.0.0", "slugify": "^1.6.6", "swagger-ui-express": "^4.6.1", + "winston": "^3.9.0", "yamljs": "^0.3.0" }, "devDependencies": { @@ -1685,6 +1687,24 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "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/@eslint/eslintrc": { "version": "1.4.1", "dev": true, @@ -2567,6 +2587,11 @@ "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", "dev": true }, + "node_modules/@types/triple-beam": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", + "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "license": "MIT" @@ -2762,6 +2787,14 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -2815,6 +2848,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "node_modules/async-mutex": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", @@ -3427,6 +3465,15 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, + "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", "dev": true, @@ -3440,9 +3487,17 @@ }, "node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, + "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", @@ -3451,12 +3506,34 @@ "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==", "dev": true }, + "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", @@ -3890,6 +3967,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", "license": "MIT", @@ -4460,6 +4542,11 @@ "pend": "~1.2.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/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -4581,6 +4668,11 @@ "dev": true, "license": "ISC" }, + "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/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5343,7 +5435,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -6316,6 +6407,11 @@ "node": ">=6" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6666,6 +6762,24 @@ "node": ">=8" } }, + "node_modules/logform": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", + "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", + "dependencies": { + "@colors/colors": "1.5.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" + } + }, + "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_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -7407,6 +7521,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/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -8259,6 +8381,14 @@ "version": "5.1.2", "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -8416,6 +8546,19 @@ "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/simple-swizzle/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/simple-update-notifier": { "version": "1.1.0", "dev": true, @@ -8586,6 +8729,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "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/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -8920,6 +9071,11 @@ "node": ">=8" } }, + "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/text-table": { "version": "0.2.0", "dev": true, @@ -9011,6 +9167,11 @@ "node": ">=12" } }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "node_modules/tslib": { "version": "2.4.1", "devOptional": true, @@ -9295,6 +9456,40 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", + "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", + "dependencies": { + "@colors/colors": "1.5.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.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "devOptional": true, @@ -10725,6 +10920,21 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@eslint/eslintrc": { "version": "1.4.1", "dev": true, @@ -11436,6 +11646,11 @@ "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", "dev": true }, + "@types/triple-beam": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", + "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" + }, "@types/webidl-conversions": { "version": "7.0.0" }, @@ -11482,8 +11697,7 @@ }, "acorn-jsx": { "version": "5.3.2", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.2.0", @@ -11569,6 +11783,11 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" + }, "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -11611,6 +11830,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "async-mutex": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", @@ -11997,8 +12221,7 @@ "cloudinary-core": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/cloudinary-core/-/cloudinary-core-2.13.0.tgz", - "integrity": "sha512-Nt0Q5I2FtenmJghtC4YZ3MZZbGg1wLm84SsxcuVwZ83OyJqG9CNIGp86CiI6iDv3QobaqBUpOT7vg+HqY5HxEA==", - "requires": {} + "integrity": "sha512-Nt0Q5I2FtenmJghtC4YZ3MZZbGg1wLm84SsxcuVwZ83OyJqG9CNIGp86CiI6iDv3QobaqBUpOT7vg+HqY5HxEA==" }, "co": { "version": "4.6.0", @@ -12012,6 +12235,30 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "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==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + } + } + }, "color-convert": { "version": "2.0.1", "dev": true, @@ -12020,8 +12267,16 @@ } }, "color-name": { - "version": "1.1.4", - "dev": true + "version": "1.1.4" + }, + "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==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "color-support": { "version": "1.1.3", @@ -12034,6 +12289,15 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -12346,6 +12610,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "encodeurl": { "version": "1.0.2" }, @@ -12667,20 +12936,17 @@ "express-async-errors": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", - "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", - "requires": {} + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==" }, "express-joi-validation": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/express-joi-validation/-/express-joi-validation-5.0.1.tgz", - "integrity": "sha512-BztcU64addcAdxys2j42pZVSnJjEyFaLxNko7YSYDUuEBtKq2XnhzYZuy9ex9Q+Fdhef+NwLXhX1djwZmShCLg==", - "requires": {} + "integrity": "sha512-BztcU64addcAdxys2j42pZVSnJjEyFaLxNko7YSYDUuEBtKq2XnhzYZuy9ex9Q+Fdhef+NwLXhX1djwZmShCLg==" }, "express-rate-limit": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", - "requires": {} + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==" }, "fast-deep-equal": { "version": "3.1.3", @@ -12732,6 +12998,11 @@ "pend": "~1.2.0" } }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "file-entry-cache": { "version": "6.0.1", "dev": true, @@ -12812,6 +13083,11 @@ "version": "3.2.7", "dev": true }, + "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==" + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -13337,8 +13613,7 @@ "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "isexe": { "version": "2.0.0", @@ -13664,8 +13939,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.2.0", @@ -14083,6 +14357,11 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -14315,6 +14594,26 @@ } } }, + "logform": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", + "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", + "requires": { + "@colors/colors": "1.5.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" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -14847,6 +15146,14 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -15423,6 +15730,11 @@ "safe-buffer": { "version": "5.1.2" }, + "safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==" + }, "safer-buffer": { "version": "2.1.2" }, @@ -15533,6 +15845,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "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==", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "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==" + } + } + }, "simple-update-notifier": { "version": "1.1.0", "dev": true, @@ -15652,6 +15979,11 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "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==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -15889,6 +16221,11 @@ "minimatch": "^3.0.4" } }, + "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==" + }, "text-table": { "version": "0.2.0", "dev": true @@ -15954,6 +16291,11 @@ "punycode": "^2.1.1" } }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "tslib": { "version": "2.4.1", "devOptional": true @@ -16141,6 +16483,34 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "winston": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", + "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", + "requires": { + "@colors/colors": "1.5.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" + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + } + }, "word-wrap": { "version": "1.2.3", "devOptional": true @@ -16177,8 +16547,7 @@ "ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "requires": {} + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==" }, "xml-name-validator": { "version": "4.0.0", diff --git a/package.json b/package.json index 928f282..e738d61 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@sentry/node": "^7.54.0", + "app-root-path": "^3.1.0", "bcrypt": "^5.1.0", "cloudinary": "^1.37.0", "cookie-parser": "~1.4.4", @@ -49,6 +50,7 @@ "passport-local": "^1.0.0", "slugify": "^1.6.6", "swagger-ui-express": "^4.6.1", + "winston": "^3.9.0", "yamljs": "^0.3.0" }, "devDependencies": { diff --git a/utils/db.js b/utils/db.js index f141007..327620a 100644 --- a/utils/db.js +++ b/utils/db.js @@ -1,13 +1,14 @@ const mongoose = require('mongoose'); -const logger = require('./logger'); +const { DB_CONNECTED, ERR_DB_CONNECTION } = require('../constant'); + const connectDatabase = async (url) => { mongoose.set('strictQuery', true); mongoose.set('strictPopulate', false); try { const connect = await mongoose.connect(url); - logger.info(`Successfully connected to ${connect.connection.host}`); + logger.info(` ${DB_CONNECTED} ${connect.connection.host}`); } catch (err) { - logger.error('A problem occured while connecting to the database', err); + logger.error(`${ERR_DB_CONNECTION} ${err.message}`); } }; diff --git a/utils/helper.js b/utils/helper.js index 6a55237..c56226c 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -7,54 +7,118 @@ const jwt = require('jsonwebtoken'); const { JSDOM } = require('jsdom'); const domPurify = createDomPurify(new JSDOM().window); const slugify = require('slugify'); +const { logger } = require('handlebars'); -const createHashedToken = (token) => { - const hashedToken = crypto.createHash('sha256').update(token).digest('hex'); - - return hashedToken; -}; - -const createToken = (payload) => { - const token = jwt.sign(payload, config.jwt.secret, { - expiresIn: config.jwt.expiry, - algorithm: 'HS256', - }); - return token; -}; - -const generateSlug = (title) => { - return slugify(title, { lower: true, strict: true }); -}; - -const sanitiseHTML = (content) => { - return domPurify.sanitize(content); -}; - -async function validateCredentials(email, password) { - const user = await Account.findOne({ email }) - .select('+password') - .populate('owner', { __v: 0 }); - if (!user) return false; - const passwordMatch = await verifyPassword(password, user.password); - if (!passwordMatch) return false; - return user; -} +/** + * Helper class + * @class Helper + * @description Helper class to hold helper functions used throughout the application lifecycle + */ -async function verifyPassword(plain, hashed) { - return await bcrypt.compare(plain, hashed); -} +class Helper { + /** + * @static createToken + * @param {string} token - A token + * @returns {string} A hashed token + * @description Creates a hashed token + * @memberof Helper + */ + static async createHashedToken(token) { + return crypto.createHash('sha256').update(token).digest('hex'); + } + + /** + * @static createToken + * @param {object} payload - A payload object + * @returns {string} A JWT token + * @description Creates a JWT token + * @memberof Helper + */ + static createToken(payload) { + const token = jwt.sign(payload, config.jwt.secret, { + expiresIn: config.jwt.expiry, + algorithm: 'HS256', + }); + return token; + } + + /** + * @static generateSlug + * @param {string} title - A title + * @returns {string} A slug + * @description Generates a slug + * @memberof Helper + */ + static generateSlug(title) { + return slugify(title, { lower: true, strict: true }); + } + + /** + * @static sanitiseHTML - sanitises HTML content + * @param {string} content - A content + * @returns {string} A sanitised content + * @description Sanitises HTML content + * @memberof Helper + */ + static sanitiseHTML = (content) => { + return domPurify.sanitize(content); + }; + + /** + * @static validateCredentials + * @param {string} email - An email + * @param {string} password - A password + * @returns {object} A user object + * @description Validates user credentials + * @memberof Helper + */ + + static async validateCredentials(email, password) { + const user = await Account.findOne({ email }) + .select('+password') + .populate('owner', { __v: 0 }); + if (!user) return false; + const passwordMatch = await verifyPassword(password, user.password); + if (!passwordMatch) return false; + return user; + } + + /** + * @static verifyPassword + * @param {string} plain - A plain text + * @param {string} hashed - A hashed text + * @returns {boolean} A boolean + * @description Verifies password + * @memberof Helper + */ + + static async verifyPassword(plain, hashed) { + return await bcrypt.compare(plain, hashed); + } + + /** + * @static tokenExpires + * @param {number} ttl - A time to live + * @returns {string} A string + * @description Returns a string + * @memberof Helper + */ + static tokenExpires(ttl) { + const mttl = parseInt(ttl, 10); + return `${mttl} minute${mttl === 1 ? 's' : ''}`; + } + + /** + * @static moduleErrLogMessager + * @param {object} error - An error object + * @returns {object} An error object + * @description Logs error messages + * @memberof Helper + */ -function tokenExpires(ttl) { - const mttl = parseInt(ttl, 10); - return `${mttl} minute${mttl === 1 ? 's' : ''}`; + static moduleErrLogMessager(error) { + return logger.error(`${error.status} - ${error.name} - ${error.message}`); + } } -module.exports = { - createHashedToken, - createToken, - generateSlug, - sanitiseHTML, - validateCredentials, - verifyPassword, - tokenExpires, -}; +module.exports = Helper; diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 0000000..d7b5ff2 --- /dev/null +++ b/utils/index.js @@ -0,0 +1,7 @@ +const Logger = require('./logger'); +const Helper = require('./helper'); + +module.exports = { + Logger, + Helper, +}; diff --git a/utils/logger.js b/utils/logger.js index 6310827..9306e24 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -1,16 +1,122 @@ -const info = (...params) => { - if (process.env.NODE_ENV !== 'test') { - console.log(...params); - } +const winston = require('winston'); +const fs = require('fs'); +const config = require('../config'); + +const { createLogger, format, transports } = winston; +const { combine, timestamp, label, printf, json, colorize } = format; + +const logFormat = printf(({ level, message, label, timestamp }) => { + return `${timestamp} ${level} : ${message}`; +}); + +const getLogToProcess = (fileOpt, consoleOpt) => { + const logArray = []; + + logArray.push( + new winston.transports.File(fileOpt), + new winston.transports.Console(consoleOpt) + ); + + return logArray; }; -const error = (...params) => { - if (process.env.NODE_ENV !== 'test') { - console.error(...params); +/** + * Logger class + * @class Logger + * @description - Logger used throughout the application lifecycle + */ + +class Logger { + constructor(options) { + this.options = options; + this.label = options.label; + this.logDir = options.logDir || `${config.root_path}/logs`; + + this._labelOptions = { + console: { + level: 'debug', + handleExceptions: true, + format: combine( + colorize({ all: true }), + printf( + (msg) => + `[${new Date(msg.timestamp).toUTCString()}]: ${msg.label} : - ${ + msg.level + }: ${msg.message}` + ) + ), + }, + file: { + level: 'debug', + filename: `${this.logDir}/app.log`, + handleExceptions: true, + maxsize: 5242880, + maxFiles: 2000, + format: winston.format.json(), + }, + Route: { + level: 'debug', + }, + }; + this.debugMode = + options.debugMode === true || options.debugMode === undefined; + this.environment = config.NODE_ENV || 'development'; } -}; -module.exports = { - info, - error, -}; + /** + * Logs all transactions during the app lifecycle + * @memberof Logger + */ + _logTransports() { + const { console, file } = this._labelOptions; + const fileOpt = { + ...file, + filename: `${this.logDir}/app.${this.environment}.log`, + }; + const logProcess = getLogToProcess(fileOpt, console); + return logProcess; + } + + /** + * Initiates a new Loger + * @param {*} options + * @returns { Object } A new logger instance + *z @memberof Logger + */ + Init() { + const logger = winston.createLogger({ + format: combine( + timestamp(), + label({ + label: this.label, + }) + ), + transports: this._logTransports(), + exitOnError: false, + }); + logger.stream = { + write(message) { + logger.info(`${message}++++ `); + }, + }; + return logger; + } + + /** + * Creates a new instance of the winston Logger with the specified configuration. + * @static + * @param { Object } options - contains configuration parameters. + * @param { String } options.logDirPath - Path to the log folder, + * the default directory is logs (optional). + * @param { Boolean } options.debugMode - If true turns on the debugging mode, default is true. + * @param { String } options.label - A name used to describe the context of the log generated. + * @returns { Object } - An instance of logger. + * @memberof Logger + */ + static createLogger(options) { + const loggerInstance = new Logger(options); + return loggerInstance.Init(); + } +} + +module.exports = Logger; From b5985a2e8ccc5e07daf1559844cd64344ad9baf0 Mon Sep 17 00:00:00 2001 From: Igbasan Olasunkanmi <86102079+Olatisunkanmi@users.noreply.github.com> Date: Mon, 3 Jul 2023 00:11:39 +0100 Subject: [PATCH 4/9] chore: revert helper --- utils/helper.js | 156 ++++++++++++++---------------------------------- 1 file changed, 46 insertions(+), 110 deletions(-) diff --git a/utils/helper.js b/utils/helper.js index c56226c..6a55237 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -7,118 +7,54 @@ const jwt = require('jsonwebtoken'); const { JSDOM } = require('jsdom'); const domPurify = createDomPurify(new JSDOM().window); const slugify = require('slugify'); -const { logger } = require('handlebars'); -/** - * Helper class - * @class Helper - * @description Helper class to hold helper functions used throughout the application lifecycle - */ - -class Helper { - /** - * @static createToken - * @param {string} token - A token - * @returns {string} A hashed token - * @description Creates a hashed token - * @memberof Helper - */ - static async createHashedToken(token) { - return crypto.createHash('sha256').update(token).digest('hex'); - } - - /** - * @static createToken - * @param {object} payload - A payload object - * @returns {string} A JWT token - * @description Creates a JWT token - * @memberof Helper - */ - static createToken(payload) { - const token = jwt.sign(payload, config.jwt.secret, { - expiresIn: config.jwt.expiry, - algorithm: 'HS256', - }); - return token; - } - - /** - * @static generateSlug - * @param {string} title - A title - * @returns {string} A slug - * @description Generates a slug - * @memberof Helper - */ - static generateSlug(title) { - return slugify(title, { lower: true, strict: true }); - } - - /** - * @static sanitiseHTML - sanitises HTML content - * @param {string} content - A content - * @returns {string} A sanitised content - * @description Sanitises HTML content - * @memberof Helper - */ - static sanitiseHTML = (content) => { - return domPurify.sanitize(content); - }; - - /** - * @static validateCredentials - * @param {string} email - An email - * @param {string} password - A password - * @returns {object} A user object - * @description Validates user credentials - * @memberof Helper - */ - - static async validateCredentials(email, password) { - const user = await Account.findOne({ email }) - .select('+password') - .populate('owner', { __v: 0 }); - if (!user) return false; - const passwordMatch = await verifyPassword(password, user.password); - if (!passwordMatch) return false; - return user; - } - - /** - * @static verifyPassword - * @param {string} plain - A plain text - * @param {string} hashed - A hashed text - * @returns {boolean} A boolean - * @description Verifies password - * @memberof Helper - */ - - static async verifyPassword(plain, hashed) { - return await bcrypt.compare(plain, hashed); - } - - /** - * @static tokenExpires - * @param {number} ttl - A time to live - * @returns {string} A string - * @description Returns a string - * @memberof Helper - */ - static tokenExpires(ttl) { - const mttl = parseInt(ttl, 10); - return `${mttl} minute${mttl === 1 ? 's' : ''}`; - } +const createHashedToken = (token) => { + const hashedToken = crypto.createHash('sha256').update(token).digest('hex'); + + return hashedToken; +}; + +const createToken = (payload) => { + const token = jwt.sign(payload, config.jwt.secret, { + expiresIn: config.jwt.expiry, + algorithm: 'HS256', + }); + return token; +}; + +const generateSlug = (title) => { + return slugify(title, { lower: true, strict: true }); +}; + +const sanitiseHTML = (content) => { + return domPurify.sanitize(content); +}; + +async function validateCredentials(email, password) { + const user = await Account.findOne({ email }) + .select('+password') + .populate('owner', { __v: 0 }); + if (!user) return false; + const passwordMatch = await verifyPassword(password, user.password); + if (!passwordMatch) return false; + return user; +} - /** - * @static moduleErrLogMessager - * @param {object} error - An error object - * @returns {object} An error object - * @description Logs error messages - * @memberof Helper - */ +async function verifyPassword(plain, hashed) { + return await bcrypt.compare(plain, hashed); +} - static moduleErrLogMessager(error) { - return logger.error(`${error.status} - ${error.name} - ${error.message}`); - } +function tokenExpires(ttl) { + const mttl = parseInt(ttl, 10); + return `${mttl} minute${mttl === 1 ? 's' : ''}`; } -module.exports = Helper; +module.exports = { + createHashedToken, + createToken, + generateSlug, + sanitiseHTML, + validateCredentials, + verifyPassword, + tokenExpires, +}; From 924457a26b7305f8a325c0caf409b5b4792535af Mon Sep 17 00:00:00 2001 From: Igbasan Olasunkanmi <86102079+Olatisunkanmi@users.noreply.github.com> Date: Mon, 3 Jul 2023 01:48:38 +0100 Subject: [PATCH 5/9] feat: class logger to log req and errors --- .eslintrc.js | 3 +++ app.js | 5 +++-- constant/index.js | 2 +- utils/logger.js | 14 +++++--------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ab5a008..7599f67 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,6 +10,9 @@ module.exports = { parserOptions: { ecmaVersion: 'latest', }, + globals: { + logger: 'readonly', + }, rules: { 'linebreak-style': ['error', 'unix'], quotes: ['error', 'single'], diff --git a/app.js b/app.js index 5c329bb..2330585 100644 --- a/app.js +++ b/app.js @@ -7,13 +7,13 @@ const { Logger } = require('./utils'); const { errorHandler } = require('./utils/errorHandler'); const cors = require('cors'); require('express-async-errors'); -const { CLIENT_URLS, STUDY_BUDDY } = require('./constant'); +const { CLIENT_URLS, ALT_CAMP } = require('./constant'); const indexRouter = require('./routes/index'); const app = express(); initializeSentry(); -global.logger = Logger.createLogger({ label: STUDY_BUDDY }); +global.logger = Logger.createLogger({ label: ALT_CAMP }); const appConfig = (app) => { app.use(Sentry.Handlers.requestHandler()); @@ -24,6 +24,7 @@ const appConfig = (app) => { origin: CLIENT_URLS, }) ); + //eslint-disable-next-line app.use(morgan('combined', { stream: logger.stream })); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: false })); diff --git a/constant/index.js b/constant/index.js index 0eaeb3a..7d3c344 100644 --- a/constant/index.js +++ b/constant/index.js @@ -2,7 +2,7 @@ module.exports = { DEVELOPMENT: 'development', PRODUCTION: 'production', - STUDY_BUDDY: 'Study Buddy', + ALT_CAMP: 'AltCamp', DB_CONNECTED: 'Database connected successfully to', ERR_DB_CONNECTION: 'Database connection failed', diff --git a/utils/logger.js b/utils/logger.js index 9306e24..93c2166 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -1,13 +1,8 @@ const winston = require('winston'); -const fs = require('fs'); const config = require('../config'); -const { createLogger, format, transports } = winston; -const { combine, timestamp, label, printf, json, colorize } = format; - -const logFormat = printf(({ level, message, label, timestamp }) => { - return `${timestamp} ${level} : ${message}`; -}); +const { format } = winston; +const { combine, timestamp, label, printf, colorize } = format; const getLogToProcess = (fileOpt, consoleOpt) => { const logArray = []; @@ -40,7 +35,7 @@ class Logger { colorize({ all: true }), printf( (msg) => - `[${new Date(msg.timestamp).toUTCString()}]: ${msg.label} : - ${ + `[${new Date(msg.timestamp).toUTCString()}]: ${msg.label}: - ${ msg.level }: ${msg.message}` ) @@ -96,7 +91,7 @@ class Logger { }); logger.stream = { write(message) { - logger.info(`${message}++++ `); + logger.info(`${message}`); }, }; return logger; @@ -113,6 +108,7 @@ class Logger { * @returns { Object } - An instance of logger. * @memberof Logger */ + // eslint-disable-next-line no-unused-vars static createLogger(options) { const loggerInstance = new Logger(options); return loggerInstance.Init(); From 939381dd14be4a500b076606accdf9354da96cc8 Mon Sep 17 00:00:00 2001 From: Tobi Balogun <98078707+tobisupreme@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:15:10 +0100 Subject: [PATCH 6/9] chore: revert helper #7c13ad9 --- model/post.js | 4 +--- model/question.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/model/post.js b/model/post.js index 74dee0c..f769d62 100644 --- a/model/post.js +++ b/model/post.js @@ -1,7 +1,5 @@ const mongoose = require('mongoose'); -const { Helper } = require('../utils'); - -const { sanitiseHTML } = Helper; +const { sanitiseHTML } = require('../utils/helper'); const postSchema = new mongoose.Schema( { diff --git a/model/question.js b/model/question.js index 05e108b..19bdf52 100644 --- a/model/question.js +++ b/model/question.js @@ -1,6 +1,5 @@ const mongoose = require('mongoose'); -const { Helper } = require('../utils'); -const { generateSlug, sanitiseHTML } = Helper; +const { generateSlug, sanitiseHTML } = require('../utils/helper'); const questionSchema = new mongoose.Schema( { From 8466dd37d001d8041fc7480cac35c868ecd10b58 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 16:32:45 +0000 Subject: [PATCH 7/9] docs: update README.md [skip ci] --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 186a1bf..b0a1f91 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # AltCampv1-backend -[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-) ## About AltCamp @@ -101,7 +101,7 @@ This project is licensed under the MIT License - see the [LICENSE](/LICENSE) fil - + @@ -111,6 +111,7 @@ This project is licensed under the MIT License - see the [LICENSE](/LICENSE) fil +
Precious Abubakar
Precious Abubakar

💻 ⚠️ 🚧 👀
Precious Abubakar
Precious Abubakar

💻 ⚠️ 🚧 👀
Tobi Balogun
Tobi Balogun

🚧 💻 ⚠️ 👀
Awosise Oluwaseun
Awosise Oluwaseun

💻 ⚠️ 👀
Samuel Adeboye Folaranmi
Samuel Adeboye Folaranmi

📖 💻 👀
Nonso Andrew Ugbodu
Nonso Andrew Ugbodu

💻 ⚠️
Module
Module

💻
From 23d1c56a3add53c4362293df64e07411b3ee9247 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 16:32:46 +0000 Subject: [PATCH 8/9] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 202bb9b..1de1417 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -90,6 +90,15 @@ "code", "test" ] + }, + { + "login": "Olatisunkanmi", + "name": "Module", + "avatar_url": "https://avatars.githubusercontent.com/u/86102079?v=4", + "profile": "https://linktr.ee/olatisun", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, From 5dc7c747cf13a73c131f4a40cfae029c24925343 Mon Sep 17 00:00:00 2001 From: Tobi Balogun <98078707+tobisupreme@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:35:52 +0100 Subject: [PATCH 9/9] Update README.md with correct web link for PDA --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0a1f91..5a26fa1 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ This project is licensed under the MIT License - see the [LICENSE](/LICENSE) fil - +
Precious Abubakar
Precious Abubakar

💻 ⚠️ 🚧 👀
Precious Abubakar
Precious Abubakar

💻 ⚠️ 🚧 👀
Tobi Balogun
Tobi Balogun

🚧 💻 ⚠️ 👀
Awosise Oluwaseun
Awosise Oluwaseun

💻 ⚠️ 👀
Samuel Adeboye Folaranmi
Samuel Adeboye Folaranmi

📖 💻 👀