From 97eccf3f208ceffdf51f49a9b5f0069b98785c6b Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Wed, 28 Aug 2024 12:44:04 -0400 Subject: [PATCH 1/6] feat: getAnniversaries --- src/helpers/overviewReportHelper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/overviewReportHelper.js b/src/helpers/overviewReportHelper.js index 52d6a2ad0..0dd9636a9 100644 --- a/src/helpers/overviewReportHelper.js +++ b/src/helpers/overviewReportHelper.js @@ -74,6 +74,9 @@ const overviewReportHelper = function () { _id: 1, firstName: 1, lastName: 1, + email: 1, + profilePic: 1, + createdDate:1, }, }, ]); From 841d1210cfff7d34cbcddcb087ed93359f5c21e5 Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Thu, 10 Oct 2024 07:01:40 -0400 Subject: [PATCH 2/6] feat: emailController.js and emailSender.js for Anniversaries --- src/controllers/emailController.js | 13 +++++++++--- src/utilities/emailSender.js | 32 +++++++++++++++++++----------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/controllers/emailController.js b/src/controllers/emailController.js index 665035ccf..9ed5352ff 100644 --- a/src/controllers/emailController.js +++ b/src/controllers/emailController.js @@ -1,5 +1,4 @@ // emailController.js -const nodemailer = require('nodemailer'); const jwt = require('jsonwebtoken'); const emailSender = require('../utilities/emailSender'); const EmailSubcriptionList = require('../models/emailSubcriptionList'); @@ -14,8 +13,16 @@ const sendEmail = async (req, res) => { console.log('to', to); - emailSender(to, subject, html); - return res.status(200).send('Email sent successfully'); + await emailSender(to, subject, html) + .then(result => { + console.log('Email sent successfully:', result); + res.status(200).send(`Email sent successfully to ${to}`); + }) + .catch(error => { + console.error('Error sending email:', error); + res.status(500).send('Error sending email'); + }); + } catch (error) { console.error('Error sending email:', error); return res.status(500).send('Error sending email'); diff --git a/src/utilities/emailSender.js b/src/utilities/emailSender.js index eb8eca3de..19834abf4 100644 --- a/src/utilities/emailSender.js +++ b/src/utilities/emailSender.js @@ -30,7 +30,7 @@ const closure = () => { if (!nextItem) return; - const { recipient, subject, message, cc, bcc, replyTo, acknowledgingReceipt } = nextItem; + const { recipient, subject, message, cc, bcc, replyTo, acknowledgingReceipt, resolve, reject} = nextItem; try { // Generate the accessToken on the fly @@ -65,6 +65,7 @@ const closure = () => { if (process.env.NODE_ENV === 'local') { logger.logInfo(`Email sent: ${JSON.stringify(result)}`); } + resolve(result); } catch (error) { if (typeof acknowledgingReceipt === 'function') { acknowledgingReceipt(error, null); @@ -74,6 +75,7 @@ const closure = () => { `Error sending email: from ${CLIENT_EMAIL} to ${recipient} subject ${subject}`, `Extra Data: cc ${cc} bcc ${bcc}`, ); + reject(error); } }, process.env.MAIL_QUEUE_INTERVAL || 1000); @@ -86,17 +88,23 @@ const closure = () => { replyTo = null, acknowledgingReceipt = null, ) { - if (process.env.sendEmail) { - queue.push({ - recipient, - subject, - message, - cc, - bcc, - replyTo, - acknowledgingReceipt, - }); - } + return new Promise((resolve, reject) => { + if (process.env.sendEmail) { + queue.push({ + recipient, + subject, + message, + cc, + bcc, + replyTo, + acknowledgingReceipt, + resolve, + reject, + }); + } else { + resolve('Email sending is disabled'); + } + }); }; return emailSender; From 7ee22172e5add7c330c81f17ee1efeace74de0c0 Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Wed, 30 Oct 2024 12:02:42 -0400 Subject: [PATCH 3/6] feat: cleaning console.log --- src/controllers/emailController.js | 2 -- src/helpers/overviewReportHelper.js | 1 - 2 files changed, 3 deletions(-) diff --git a/src/controllers/emailController.js b/src/controllers/emailController.js index 9ed5352ff..13de952e3 100644 --- a/src/controllers/emailController.js +++ b/src/controllers/emailController.js @@ -11,8 +11,6 @@ const sendEmail = async (req, res) => { try { const { to, subject, html } = req.body; - console.log('to', to); - await emailSender(to, subject, html) .then(result => { console.log('Email sent successfully:', result); diff --git a/src/helpers/overviewReportHelper.js b/src/helpers/overviewReportHelper.js index 0dd9636a9..cb1cd8edb 100644 --- a/src/helpers/overviewReportHelper.js +++ b/src/helpers/overviewReportHelper.js @@ -76,7 +76,6 @@ const overviewReportHelper = function () { lastName: 1, email: 1, profilePic: 1, - createdDate:1, }, }, ]); From b8a07c2e09272da815fdacdee8bad619d7992309 Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Wed, 4 Dec 2024 11:07:23 -0400 Subject: [PATCH 4/6] Update emailSender.js --- src/utilities/emailSender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/emailSender.js b/src/utilities/emailSender.js index 839004e52..66d067c1d 100644 --- a/src/utilities/emailSender.js +++ b/src/utilities/emailSender.js @@ -84,7 +84,7 @@ const processQueue = async () => { } catch (error) { if (typeof acknowledgingReceipt === 'function') { acknowledgingReceipt(error, null); - } + }; logger.logException( error, `Error sending email: from ${CLIENT_EMAIL} to ${recipient} subject ${subject}`, From 3283ce4bafa14d5d8b694f6a551640d67ec74fd1 Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Wed, 4 Dec 2024 11:56:51 -0400 Subject: [PATCH 5/6] s reverts commit b8a07c2e09272da815fdacdee8bad619d7992309. --- src/utilities/emailSender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/emailSender.js b/src/utilities/emailSender.js index 66d067c1d..839004e52 100644 --- a/src/utilities/emailSender.js +++ b/src/utilities/emailSender.js @@ -84,7 +84,7 @@ const processQueue = async () => { } catch (error) { if (typeof acknowledgingReceipt === 'function') { acknowledgingReceipt(error, null); - }; + } logger.logException( error, `Error sending email: from ${CLIENT_EMAIL} to ${recipient} subject ${subject}`, From 0c544bff3ba730cac73347f7d70ab4fb154e7a22 Mon Sep 17 00:00:00 2001 From: Carlos Gomez Date: Wed, 4 Dec 2024 12:13:02 -0400 Subject: [PATCH 6/6] Revert "Merge branch 'development' into Carlos-Anniversary-Celebrated" This reverts commit fdc8fe3f3c5e4e705d6eb42eecfc24293a13db9f, reversing changes made to 7ee22172e5add7c330c81f17ee1efeace74de0c0. --- .DS_Store | Bin 8196 -> 8196 bytes package-lock.json | 608 +++---- package.json | 1 - .../addNonHgnEmailSubscription.md | 23 - .../confirmNonHgnEmailSubscription.md | 18 - .../removeNonHgnEmailSubscription.md | 10 - requirements/emailController/sendEmail.md | 10 - .../emailController/sendEmailToAll.md | 26 - .../updateEmailSubscription.md | 20 - .../informationController/addInformation.md | 16 - .../deleteInformation.md | 13 - .../informationController/getInformation.md | 13 - .../updateInformation.md | 13 - .../createPopPopupEditor.md | 14 - .../getAllPopupEditors.md | 10 - .../getPopupEditorById.md | 10 - .../updatePopupEditor.md | 11 - .../deleteReason.md | 16 - .../getAllReasons.md | 14 - .../getSingleReason.md | 15 - .../reasonSchedulingController/patchReason.md | 16 - .../reasonSchedulingController/postReason.md | 18 - requirements/taskController/deleteTask.md | 15 - .../taskController/deleteTaskByWBS.md | 15 - requirements/taskController/fixTasks.md | 11 - requirements/taskController/getTaskById.md | 14 - requirements/taskController/getTasks.md | 12 - .../taskController/getTasksByUserId.md | 12 - .../taskController/getTasksForTeamsByUser.md | 12 - requirements/taskController/getWBSId.md | 12 - requirements/taskController/importTask.md | 14 - requirements/taskController/moveTask.md | 13 - requirements/taskController/postTask.md | 14 - requirements/taskController/sendReviewReq.md | 12 - requirements/taskController/swap.md | 16 - .../taskController/updateAllParents.md | 12 - requirements/taskController/updateNum.md | 16 - requirements/taskController/updateTask.md | 16 - .../taskController/updateTaskStatus.md | 12 - .../timeZoneAPIController/getTImeZone.md | 20 - .../getTimeZoneProfileInitialSetup.md | 20 - src/controllers/emailController.js | 148 +- src/controllers/emailController.spec.js | 146 -- src/controllers/informationController.js | 6 +- src/controllers/informationController.spec.js | 392 ----- src/controllers/jobsController.js | 131 -- src/controllers/logincontroller.js | 2 +- src/controllers/logincontroller.spec.js | 2 +- src/controllers/ownerMessageController.js | 13 +- src/controllers/popupEditorController.spec.js | 163 -- .../profileInitialSetupController.js | 67 +- src/controllers/projectController.js | 11 +- .../reasonSchedulingController.spec.js | 626 ------- src/controllers/taskController.js | 5 +- src/controllers/taskController.spec.js | 1555 ----------------- src/controllers/teamController.js | 163 +- src/controllers/timeEntryController.js | 187 +- src/controllers/timeZoneAPIController.js | 13 +- src/controllers/timeZoneAPIController.spec.js | 316 ---- src/controllers/titleController.js | 237 +-- src/controllers/userProfileController.js | 314 +--- src/cronjobs/userProfileJobs.js | 11 - src/helpers/dashboardhelper.js | 4 +- src/helpers/taskHelper.js | 8 +- src/helpers/userHelper.js | 180 +- src/models/jobs.js | 17 - src/models/team.js | 4 +- src/models/timeentry.js | 2 - src/models/title.js | 4 +- src/models/userProfile.js | 1 - src/routes/informationRouter.test.js | 145 -- src/routes/jobsRouter.js | 14 - src/routes/reasonRouter.test.js | 338 ---- src/routes/teamRouter.js | 4 - src/routes/timeZoneAPIRoutes.test.js | 208 --- src/routes/timeentryRouter.js | 12 +- src/routes/titleRouter.js | 6 +- src/routes/userProfileRouter.js | 14 - src/startup/db.js | 2 +- src/startup/middleware.js | 34 +- src/startup/routes.js | 3 +- src/test/createTestPermissions.js | 18 +- src/utilities/createInitialPermissions.js | 16 +- src/utilities/emailSender.js | 149 +- 84 files changed, 653 insertions(+), 6201 deletions(-) delete mode 100644 requirements/emailController/addNonHgnEmailSubscription.md delete mode 100644 requirements/emailController/confirmNonHgnEmailSubscription.md delete mode 100644 requirements/emailController/removeNonHgnEmailSubscription.md delete mode 100644 requirements/emailController/sendEmail.md delete mode 100644 requirements/emailController/sendEmailToAll.md delete mode 100644 requirements/emailController/updateEmailSubscription.md delete mode 100644 requirements/informationController/addInformation.md delete mode 100644 requirements/informationController/deleteInformation.md delete mode 100644 requirements/informationController/getInformation.md delete mode 100644 requirements/informationController/updateInformation.md delete mode 100644 requirements/popUpEditorController/createPopPopupEditor.md delete mode 100644 requirements/popUpEditorController/getAllPopupEditors.md delete mode 100644 requirements/popUpEditorController/getPopupEditorById.md delete mode 100644 requirements/popUpEditorController/updatePopupEditor.md delete mode 100644 requirements/reasonSchedulingController/deleteReason.md delete mode 100644 requirements/reasonSchedulingController/getAllReasons.md delete mode 100644 requirements/reasonSchedulingController/getSingleReason.md delete mode 100644 requirements/reasonSchedulingController/patchReason.md delete mode 100644 requirements/reasonSchedulingController/postReason.md delete mode 100644 requirements/taskController/deleteTask.md delete mode 100644 requirements/taskController/deleteTaskByWBS.md delete mode 100644 requirements/taskController/fixTasks.md delete mode 100644 requirements/taskController/getTaskById.md delete mode 100644 requirements/taskController/getTasks.md delete mode 100644 requirements/taskController/getTasksByUserId.md delete mode 100644 requirements/taskController/getTasksForTeamsByUser.md delete mode 100644 requirements/taskController/getWBSId.md delete mode 100644 requirements/taskController/importTask.md delete mode 100644 requirements/taskController/moveTask.md delete mode 100644 requirements/taskController/postTask.md delete mode 100644 requirements/taskController/sendReviewReq.md delete mode 100644 requirements/taskController/swap.md delete mode 100644 requirements/taskController/updateAllParents.md delete mode 100644 requirements/taskController/updateNum.md delete mode 100644 requirements/taskController/updateTask.md delete mode 100644 requirements/taskController/updateTaskStatus.md delete mode 100644 requirements/timeZoneAPIController/getTImeZone.md delete mode 100644 requirements/timeZoneAPIController/getTimeZoneProfileInitialSetup.md delete mode 100644 src/controllers/emailController.spec.js delete mode 100644 src/controllers/informationController.spec.js delete mode 100644 src/controllers/jobsController.js delete mode 100644 src/controllers/popupEditorController.spec.js delete mode 100644 src/controllers/reasonSchedulingController.spec.js delete mode 100644 src/controllers/taskController.spec.js delete mode 100644 src/controllers/timeZoneAPIController.spec.js delete mode 100644 src/models/jobs.js delete mode 100644 src/routes/informationRouter.test.js delete mode 100644 src/routes/jobsRouter.js delete mode 100644 src/routes/reasonRouter.test.js delete mode 100644 src/routes/timeZoneAPIRoutes.test.js diff --git a/.DS_Store b/.DS_Store index 92031f7e6e3c3e6689a9e5f7a26b8c4b0f096535..8d8d400ad748d55ea99c549e9cf8a44bed35dab6 100644 GIT binary patch delta 62 zcmZp1XmQwJB&f=Cj)8%JjX{qggQ1k6m?4{?GB@AFB`GIA2`J7{oe@59%Lra6wsi LD0_3YU?(2{2kH|w diff --git a/package-lock.json b/package-lock.json index 9e07004c6..3c4e3e99e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -619,9 +619,9 @@ }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true } } @@ -691,18 +691,18 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", - "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.24.7" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true } } @@ -1477,9 +1477,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -1593,9 +1593,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -1710,9 +1710,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -1825,9 +1825,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -1911,9 +1911,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2023,9 +2023,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { @@ -2039,9 +2039,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2131,9 +2131,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { @@ -2175,9 +2175,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2298,9 +2298,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { @@ -2314,9 +2314,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2857,7 +2857,7 @@ "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "@types/methods": { @@ -3081,7 +3081,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { "version": "3.1.6", @@ -4739,7 +4739,7 @@ "bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "bignumber.js": { "version": "9.0.2", @@ -4797,11 +4797,6 @@ } } }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4874,7 +4869,7 @@ "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "buffer-from": { "version": "1.1.2", @@ -4928,33 +4923,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "requires": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - } - }, - "cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "requires": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - } - }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4977,9 +4945,9 @@ "dev": true }, "cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, "clean-stack": { @@ -5068,7 +5036,7 @@ "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" }, "clone-deep": { "version": "4.0.1", @@ -5108,7 +5076,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "colorette": { "version": "2.0.19", @@ -5132,7 +5100,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "component-emitter": { "version": "1.3.1", @@ -5142,7 +5110,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "confusing-browser-globals": { "version": "1.0.11", @@ -5186,7 +5154,7 @@ "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cookiejar": { "version": "2.1.4", @@ -5258,9 +5226,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -5353,23 +5321,6 @@ } } }, - "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5665,7 +5616,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { "version": "1.4.81", @@ -5687,7 +5638,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "end-of-stream": { "version": "1.4.4", @@ -5832,12 +5783,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint": { "version": "8.47.0", @@ -6767,7 +6718,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "event-target-shim": { "version": "5.0.1", @@ -6930,7 +6881,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "safe-buffer": { "version": "5.2.1", @@ -6975,7 +6926,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fast-safe-stringify": { @@ -7049,7 +7000,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -7186,7 +7137,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-constants": { "version": "1.0.0", @@ -7202,7 +7153,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.3", @@ -7509,7 +7460,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "has-property-descriptors": { "version": "1.0.0", @@ -7658,9 +7609,9 @@ } }, "import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "requires": { "pkg-dir": "^4.2.0", @@ -7715,7 +7666,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -7727,7 +7678,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7921,7 +7872,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -8035,13 +7986,13 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "istanbul-lib-coverage": { "version": "3.2.2", @@ -8050,9 +8001,9 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "requires": { "@babel/core": "^7.23.9", @@ -8083,27 +8034,27 @@ } }, "@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true }, "@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -8120,26 +8071,26 @@ } }, "@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "requires": { - "@babel/types": "^7.25.6", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "requires": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -8152,6 +8103,34 @@ } } }, + "@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "requires": { + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "requires": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "requires": { + "@babel/types": "^7.24.7" + } + }, "@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -8163,15 +8142,16 @@ } }, "@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "requires": { + "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" } }, "@babel/helper-simple-access": { @@ -8184,10 +8164,19 @@ "@babel/types": "^7.24.7" } }, + "@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "requires": { + "@babel/types": "^7.24.7" + } + }, "@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true }, "@babel/helper-validator-identifier": { @@ -8197,19 +8186,19 @@ "dev": true }, "@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true }, "@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "requires": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/highlight": { @@ -8225,47 +8214,47 @@ } }, "@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.25.6" - } + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "dev": true }, "@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "requires": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", "dev": true, "requires": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-string-parser": "^7.24.7", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } @@ -8304,23 +8293,23 @@ }, "dependencies": { "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true } } }, "browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" } }, "convert-source-map": { @@ -8330,9 +8319,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.22.tgz", - "integrity": "sha512-tKYm5YHPU1djz0O+CGJ+oJIvimtsCcwR2Z9w7Skh08lUdyzXY5djods3q+z2JkWdb7tCcmM//eVavSRAiaPRNg==", + "version": "1.4.815", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.815.tgz", + "integrity": "sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==", "dev": true }, "lru-cache": { @@ -8345,15 +8334,15 @@ } }, "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true }, "yallist": { @@ -8391,9 +8380,9 @@ } }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true }, "supports-color": { @@ -8463,9 +8452,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -8644,9 +8633,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -8788,9 +8777,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -8984,9 +8973,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9103,9 +9092,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9203,9 +9192,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9447,9 +9436,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9563,9 +9552,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9762,9 +9751,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9891,9 +9880,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -10005,9 +9994,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -10103,9 +10092,9 @@ "dev": true }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true }, "supports-color": { @@ -10148,9 +10137,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -10236,9 +10225,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -10363,9 +10352,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -10493,7 +10482,7 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json5": { @@ -10848,7 +10837,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "lodash.merge": { "version": "4.6.2", @@ -11047,7 +11036,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "memory-pager": { "version": "1.5.0", @@ -11058,7 +11047,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "merge-stream": { "version": "2.0.0", @@ -11069,7 +11058,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { "version": "4.0.5", @@ -11396,7 +11385,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "negotiator": { @@ -11560,18 +11549,10 @@ } } }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.12.0", @@ -12290,7 +12271,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } @@ -12380,30 +12361,13 @@ "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==" }, "parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" }, - "parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "requires": { - "entities": "^4.4.0" - } - }, - "parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", - "requires": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12412,12 +12376,12 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -12433,7 +12397,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "pend": { "version": "1.2.0", @@ -13186,7 +13150,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "optional": true, "requires": { "memory-pager": "^1.0.2" @@ -13936,7 +13900,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, "strip-final-newline": { @@ -14127,13 +14091,13 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, "tmp": { @@ -14151,7 +14115,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" }, "to-regex-range": { "version": "5.0.1", @@ -14178,7 +14142,7 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tsconfig-paths": { "version": "3.14.2", @@ -14344,12 +14308,12 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "requires": { "escalade": "^3.1.2", @@ -14357,15 +14321,15 @@ }, "dependencies": { "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true } } @@ -14382,17 +14346,17 @@ "url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { "version": "3.4.0", @@ -14417,9 +14381,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { @@ -14456,7 +14420,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "walker": { "version": "1.0.8", @@ -14470,12 +14434,12 @@ "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -14593,7 +14557,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 91e73f39a..8b88744dc 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "babel-plugin-module-resolver": "^5.0.0", "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", - "cheerio": "^1.0.0-rc.12", "cors": "^2.8.4", "cron": "^1.8.2", "dotenv": "^5.0.1", diff --git a/requirements/emailController/addNonHgnEmailSubscription.md b/requirements/emailController/addNonHgnEmailSubscription.md deleted file mode 100644 index f5748f142..000000000 --- a/requirements/emailController/addNonHgnEmailSubscription.md +++ /dev/null @@ -1,23 +0,0 @@ -# Add Non-HGN Email Subscription Function - -## Negative Cases - -1. ❌ **Returns error 400 if `email` field is missing from the request** - - Ensures that the function checks for the presence of the `email` field in the request body and responds with a `400` status code if it's missing. - -2. ❌ **Returns error 400 if the provided `email` already exists in the subscription list** - - This case checks that the function responds with a `400` status code and a message indicating that the email is already subscribed. - -3. ❌ **Returns error 500 if there is an internal error while checking the subscription list** - - Covers scenarios where there's an issue querying the `EmailSubscriptionList` collection for the provided email (e.g., database connection issues). - -4. ❌ **Returns error 500 if there is an error sending the confirmation email** - - This case handles any issues that occur while calling the `emailSender` function, such as network errors or service unavailability. - -## Positive Cases - -1. ❌ **Returns status 200 when a new email is successfully subscribed** - - Ensures that the function successfully creates a JWT token, constructs the email, and sends the subscription confirmation email to the user. - -2. ❌ **Successfully sends a confirmation email containing the correct link** - - Verifies that the generated JWT token is correctly included in the confirmation link sent to the user in the email body. diff --git a/requirements/emailController/confirmNonHgnEmailSubscription.md b/requirements/emailController/confirmNonHgnEmailSubscription.md deleted file mode 100644 index d5e1367af..000000000 --- a/requirements/emailController/confirmNonHgnEmailSubscription.md +++ /dev/null @@ -1,18 +0,0 @@ -# Confirm Non-HGN Email Subscription Function Tests - -## Negative Cases -1. ✅ **Returns error 400 if `token` field is missing from the request** - - (Test: `should return 400 if token is not provided`) - -2. ✅ **Returns error 401 if the provided `token` is invalid or expired** - - (Test: `should return 401 if token is invalid`) - -3. ✅ **Returns error 400 if the decoded `token` does not contain a valid `email` field** - - (Test: `should return 400 if email is missing from payload`) - -4. ❌ **Returns error 500 if there is an internal error while saving the new email subscription** - -## Positive Cases -1. ❌ **Returns status 200 when a new email is successfully subscribed** - -2. ❌ **Returns status 200 if the email is already subscribed (duplicate email)** diff --git a/requirements/emailController/removeNonHgnEmailSubscription.md b/requirements/emailController/removeNonHgnEmailSubscription.md deleted file mode 100644 index af793e2a9..000000000 --- a/requirements/emailController/removeNonHgnEmailSubscription.md +++ /dev/null @@ -1,10 +0,0 @@ -# Remove Non-HGN Email Subscription Function Tests - -## Negative Cases -1. ✅ **Returns error 400 if `email` field is missing from the request** - - (Test: `should return 400 if email is missing`) - -2. ❌ **Returns error 500 if there is an internal error while deleting the email subscription** - -## Positive Cases -1. ❌ **Returns status 200 when an email is successfully unsubscribed** diff --git a/requirements/emailController/sendEmail.md b/requirements/emailController/sendEmail.md deleted file mode 100644 index 7ca9a482c..000000000 --- a/requirements/emailController/sendEmail.md +++ /dev/null @@ -1,10 +0,0 @@ -# Send Email Function - -## Negative Cases - -1. ❌ **Returns error 400 if `to`, `subject`, or `html` fields are missing from the request** -2. ❌ **Returns error 500 if there is an internal error while sending the email** - -## Positive Cases - -1. ✅ **Returns status 200 when email is successfully sent with `to`, `subject`, and `html` fields provided** diff --git a/requirements/emailController/sendEmailToAll.md b/requirements/emailController/sendEmailToAll.md deleted file mode 100644 index 32a09fed6..000000000 --- a/requirements/emailController/sendEmailToAll.md +++ /dev/null @@ -1,26 +0,0 @@ -# Send Email to All Function - -## Negative Cases - -1. ❌ **Returns error 400 if `subject` or `html` fields are missing from the request** - - The request should be rejected if either the `subject` or `html` content is not provided in the request body. - -2. ❌ **Returns error 500 if there is an internal error while fetching users** - - This case covers scenarios where there's an error fetching users from the `userProfile` collection (e.g., database connection issues). - -3. ❌ **Returns error 500 if there is an internal error while fetching the subscription list** - - This case covers scenarios where there's an error fetching emails from the `EmailSubcriptionList` collection. - -4. ❌ **Returns error 500 if there is an error sending emails** - - This case handles any issues that occur while calling the `emailSender` function, such as network errors or service unavailability. - -## Positive Cases - -1. ❌ **Returns status 200 when emails are successfully sent to all active users** - - Ensures that the function sends emails correctly to all users meeting the criteria (`isActive` and `EmailSubcriptionList`). - -2. ❌ **Returns status 200 when emails are successfully sent to all users in the subscription list** - - Verifies that the function sends emails to all users in the `EmailSubcriptionList`, including the unsubscribe link in the email body. - -3. ❌ **Combines user and subscription list emails successfully** - - Ensures that the function correctly sends emails to both active users and the subscription list without issues. diff --git a/requirements/emailController/updateEmailSubscription.md b/requirements/emailController/updateEmailSubscription.md deleted file mode 100644 index bcafa5a28..000000000 --- a/requirements/emailController/updateEmailSubscription.md +++ /dev/null @@ -1,20 +0,0 @@ -# Update Email Subscriptions Function - -## Negative Cases - -1. ❌ **Returns error 400 if `emailSubscriptions` field is missing from the request** - - This ensures that the function checks for the presence of the `emailSubscriptions` field in the request body and responds with a `400` status code if it's missing. - -2. ❌ **Returns error 400 if `email` field is missing from the requestor object** - - Ensures that the function requires an `email` field within the `requestor` object in the request body and returns `400` if it's absent. - -3. ❌ **Returns error 404 if the user with the provided `email` is not found** - - This checks that the function correctly handles cases where no user exists with the given `email` and responds with a `404` status code. - -4. ✅ **Returns error 500 if there is an internal error while updating the user profile** - - Covers scenarios where there's a database error while updating the user's email subscriptions. - -## Positive Cases - -1. ❌ **Returns status 200 and the updated user when email subscriptions are successfully updated** - - Ensures that the function updates the `emailSubscriptions` field for the user and returns the updated user document along with a `200` status code. diff --git a/requirements/informationController/addInformation.md b/requirements/informationController/addInformation.md deleted file mode 100644 index d62066735..000000000 --- a/requirements/informationController/addInformation.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Add Information - -> ## Positive case -1. ✅ Returns 201 if adding new information successfullyn and no cache. -2. ✅ Returns if adding new information successfully and hascache. - -> ## Negative case -1. ✅ Returns error 500 if if there are no information in the database and any error occurs when finding the infoName. -2. ✅ Returns error 400 if if there are duplicate infoName in the database. -3. ✅ Returns error 400 if if there are issues when saving new informations. -4. ✅ Returns error 400 if if there are errors when saving the new information. - -> ## Edge case \ No newline at end of file diff --git a/requirements/informationController/deleteInformation.md b/requirements/informationController/deleteInformation.md deleted file mode 100644 index fb5c3c867..000000000 --- a/requirements/informationController/deleteInformation.md +++ /dev/null @@ -1,13 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Delete Information - -> ## Positive case -1. ✅ Returns 200 if deleting informations successfull and no cache. -2. ✅ Returns if deleting informations successfully and has cache. - -> ## Negative case -1. ✅ Returns error 400 if if there is any error when finding the information by information id. - -> ## Edge case \ No newline at end of file diff --git a/requirements/informationController/getInformation.md b/requirements/informationController/getInformation.md deleted file mode 100644 index bd8976a5a..000000000 --- a/requirements/informationController/getInformation.md +++ /dev/null @@ -1,13 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Get Information - -> ## Positive case -1. ✅ Returns 200 if the informations key exists in NodeCache. -2. ✅ Returns 200 if there are information in the database. - -> ## Negative case -1. ✅ Returns error 404 if if there are no information in the database and any error occurs when finding the information. - -> ## Edge case \ No newline at end of file diff --git a/requirements/informationController/updateInformation.md b/requirements/informationController/updateInformation.md deleted file mode 100644 index 709bcba54..000000000 --- a/requirements/informationController/updateInformation.md +++ /dev/null @@ -1,13 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Update Information - -> ## Positive case -1. ✅ Returns 200 if updating informations successfully when no cache. -2. ✅ Returns if updating informations successfully when hascache. - -> ## Negative case -1. ✅ Returns error 400 if if there is any error when finding the information by information id. - -> ## Edge case \ No newline at end of file diff --git a/requirements/popUpEditorController/createPopPopupEditor.md b/requirements/popUpEditorController/createPopPopupEditor.md deleted file mode 100644 index 0afcaa740..000000000 --- a/requirements/popUpEditorController/createPopPopupEditor.md +++ /dev/null @@ -1,14 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# createPopPopupEditor Function - -> ### Positive case - -> 1. ✅ Should return 201 and the new pop-up editor on success - -> ### Negative case - -> 1. ✅ Should return 403 if user does not have permission to create a pop-up editor -> 2. ✅ Should return 400 if the request body is missing required fields -> 3. ✅ Should return 500 if there is an error saving the new pop-up editor to the database diff --git a/requirements/popUpEditorController/getAllPopupEditors.md b/requirements/popUpEditorController/getAllPopupEditors.md deleted file mode 100644 index f42f93c1a..000000000 --- a/requirements/popUpEditorController/getAllPopupEditors.md +++ /dev/null @@ -1,10 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getAllPopupEditors Function - -> ## Positive case -> 1. ✅ Should return 200 and all pop-up editors on success - -> ## Negative case -> 1. ✅ Should return 404 if there is an error retrieving the pop-up editors from the database diff --git a/requirements/popUpEditorController/getPopupEditorById.md b/requirements/popUpEditorController/getPopupEditorById.md deleted file mode 100644 index 013096ed9..000000000 --- a/requirements/popUpEditorController/getPopupEditorById.md +++ /dev/null @@ -1,10 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getPopupEditorById Function - -> ## Positive case -> 1. ✅ Should return 200 and the pop-up editor on success - -> ## Negative case -> 1. ✅ Should return 404 if the pop-up editor is not found diff --git a/requirements/popUpEditorController/updatePopupEditor.md b/requirements/popUpEditorController/updatePopupEditor.md deleted file mode 100644 index c4e5f5904..000000000 --- a/requirements/popUpEditorController/updatePopupEditor.md +++ /dev/null @@ -1,11 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updatePopupEditor Function - -> ## Positive case -> 1. ✅ Should return 200 and the updated pop-up editor on success - - -> ## Negative case -> 1. ✅ Should return 404 if the pop-up editor is not found diff --git a/requirements/reasonSchedulingController/deleteReason.md b/requirements/reasonSchedulingController/deleteReason.md deleted file mode 100644 index 8df5ccb16..000000000 --- a/requirements/reasonSchedulingController/deleteReason.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# deleteReason - -> ## Positive case -1. ✅ Receives a POST request in the **/api/reason/:userId/** route. -2. ✅ Return 200 if delete reason successfully. - -> ## Negative case -1. ✅ Returns 403 when no permission to delete. -2. ✅ Returns 404 when error in finding user Id. -3. ✅ Returns 404 when error in finding reason. -4. ✅ Returns 500 when error in deleting. - -> ## Edge case \ No newline at end of file diff --git a/requirements/reasonSchedulingController/getAllReasons.md b/requirements/reasonSchedulingController/getAllReasons.md deleted file mode 100644 index 58499a41b..000000000 --- a/requirements/reasonSchedulingController/getAllReasons.md +++ /dev/null @@ -1,14 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getAllReasons - -> ## Positive case -1. ✅ Receives a GET request in the **/api/reason/:userId** route. -2. ✅ Return 200 if get schedule reason successfully. - -> ## Negative case -1. ✅ Returns 404 when error in finding user by Id. -2. ✅ Returns 400 when any error in fetching the user - -> ## Edge case \ No newline at end of file diff --git a/requirements/reasonSchedulingController/getSingleReason.md b/requirements/reasonSchedulingController/getSingleReason.md deleted file mode 100644 index bc81fd9d9..000000000 --- a/requirements/reasonSchedulingController/getSingleReason.md +++ /dev/null @@ -1,15 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getSingleReason - -> ## Positive case -1. ✅ Receives a GET request in the **/api/reason/single/:userId** route. -2. ✅ Return 200 if not found schedule reason and return empty object successfully. -3. ✅ Return 200 if found schedule reason and return reason successfully. - -> ## Negative case -1. ✅ Returns 404 when any error in find user by Id -2. ✅ Returns 400 when any error in fetching the user - -> ## Edge case \ No newline at end of file diff --git a/requirements/reasonSchedulingController/patchReason.md b/requirements/reasonSchedulingController/patchReason.md deleted file mode 100644 index 6e84a8ba7..000000000 --- a/requirements/reasonSchedulingController/patchReason.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# patchReason - -> ## Positive case -1. ✅ Receives a POST request in the **/api/breason/** route. -2. ✅ Return 200 if updated schedule reason and send blue sqaure email successfully. - -> ## Negative case -1. ✅ Returns 400 for not providing reason. -2. ✅ Returns 404 when error in finding user Id. -3. ✅ Returns 404 when not finding provided reason. -4. ✅ Returns 400 when any error in saving. - -> ## Edge case \ No newline at end of file diff --git a/requirements/reasonSchedulingController/postReason.md b/requirements/reasonSchedulingController/postReason.md deleted file mode 100644 index ac8ea8f2d..000000000 --- a/requirements/reasonSchedulingController/postReason.md +++ /dev/null @@ -1,18 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# postReason - -> ## Positive case -1. ✅ Receives a POST request in the **/api/reason/** route. -2. ✅ Return 200 if s dchedule reason and send blue sqaure email successfully. - -> ## Negative case -1. ✅ Returns 400 for warning to choose Sunday. -2. ✅ Returns 400 for warning to choose a funture date. -3. ✅ Returns 400 for not providing reason. -4. ✅ Returns 404 when error in finding user Id. -5. ✅ Returns 403 when duplicate reason to the date. -6. ✅ Returns 400 when any error in saving. - -> ## Edge case \ No newline at end of file diff --git a/requirements/taskController/deleteTask.md b/requirements/taskController/deleteTask.md deleted file mode 100644 index 4f986e2a9..000000000 --- a/requirements/taskController/deleteTask.md +++ /dev/null @@ -1,15 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# deleteTask Function - -> ## Positive case -1. ✅ Returns status 200 on successful deletion. - -> ## Negative case -1. ✅ Returns status 400 if either no record is found in Task collection or some error occurs while saving the tasks. - -2. ✅ Returns status 403 if the request.body.requestor does not have `deleteTask` permission. - - -> ## Edge case diff --git a/requirements/taskController/deleteTaskByWBS.md b/requirements/taskController/deleteTaskByWBS.md deleted file mode 100644 index 20ef90624..000000000 --- a/requirements/taskController/deleteTaskByWBS.md +++ /dev/null @@ -1,15 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# deleteTaskByWBS Function - -> ## Positive case -1. ✅ Returns status 200 on successful deletion. - -> ## Negative case -1. ✅ Returns status 400 if either no record is found in Task collection or some error occurs while saving the tasks. - -2. ✅ Returns status 403 if the request.body.requestor does not have `deleteTask` permission. - - -> ## Edge case diff --git a/requirements/taskController/fixTasks.md b/requirements/taskController/fixTasks.md deleted file mode 100644 index f6a80846a..000000000 --- a/requirements/taskController/fixTasks.md +++ /dev/null @@ -1,11 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateAllParents Function - -> ## Positive case -1. ✅ Returns status 200 on without performing an operation. - -> ## Negative case - -> ## Edge case diff --git a/requirements/taskController/getTaskById.md b/requirements/taskController/getTaskById.md deleted file mode 100644 index 88e7e1d03..000000000 --- a/requirements/taskController/getTaskById.md +++ /dev/null @@ -1,14 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getTaskById Function - -> ## Positive case -1. ✅ Returns status 200 on successfully getting a taskById. - -> ## Negative case -1. ✅ Returns status 400 if either req.params.id is missing or is `undefined` or if no task is found in Task collection. - -2. ✅ Returns status 500 if some error occurs. - -> ## Edge case diff --git a/requirements/taskController/getTasks.md b/requirements/taskController/getTasks.md deleted file mode 100644 index 8cd6f08e7..000000000 --- a/requirements/taskController/getTasks.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getTasks Function - -> ## Positive case -1. ✅ Returns status 200 on successfully querying the Task collection. - -> ## Negative case -1. ✅ Returns status 404 if any error occurs while querying the Task collection. - -> ## Edge case diff --git a/requirements/taskController/getTasksByUserId.md b/requirements/taskController/getTasksByUserId.md deleted file mode 100644 index 7f482ef07..000000000 --- a/requirements/taskController/getTasksByUserId.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getTasksByUserId Function - -> ## Positive case -1. ✅ Returns status 200 on successfully getting the tasks. - -> ## Negative case -1. ✅ Returns status 400 if some error occurs. - -> ## Edge case diff --git a/requirements/taskController/getTasksForTeamsByUser.md b/requirements/taskController/getTasksForTeamsByUser.md deleted file mode 100644 index 4eae8defa..000000000 --- a/requirements/taskController/getTasksForTeamsByUser.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getTasksForTeamsByUser Function - -> ## Positive case -1. ✅ Returns status 200 on successfully fetching teams data. - -> ## Negative case -1. ✅ Returns status 400 if some error occurs. - -> ## Edge case diff --git a/requirements/taskController/getWBSId.md b/requirements/taskController/getWBSId.md deleted file mode 100644 index 8efb2290a..000000000 --- a/requirements/taskController/getWBSId.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# getWBSId Function - -> ## Positive case -1. ✅ Returns status 200 on successfully querying the WBS collection. - -> ## Negative case -1. ✅ Returns status 404 if any error occurs while querying the WBS collection. - -> ## Edge case diff --git a/requirements/taskController/importTask.md b/requirements/taskController/importTask.md deleted file mode 100644 index 8626209aa..000000000 --- a/requirements/taskController/importTask.md +++ /dev/null @@ -1,14 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# importTask Function - -> ## Positive case -1. ✅ Returns status 201 on successfully creating and saving the new Task. - -> ## Negative case -1. ✅ Returns status 400 if any error occurs while saving the Task. - -2. ✅ Returns status 403 if request.body.requestor is missing permission for importTask. - -> ## Edge case diff --git a/requirements/taskController/moveTask.md b/requirements/taskController/moveTask.md deleted file mode 100644 index 68794e051..000000000 --- a/requirements/taskController/moveTask.md +++ /dev/null @@ -1,13 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateNum Function - -> ## Positive case -1. ✅ Returns status 200 on successful updation. - -> ## Negative case -1. ✅ Returns status 400 if either request.body.fromNum request.body.toNum is missing or some error occurs while saving the tasks. - - -> ## Edge case diff --git a/requirements/taskController/postTask.md b/requirements/taskController/postTask.md deleted file mode 100644 index 971b9a04a..000000000 --- a/requirements/taskController/postTask.md +++ /dev/null @@ -1,14 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# postTask Function - -> ## Positive case -1. ✅ Returns status 201 on successfully posting the new task. - -> ## Negative case -1. ✅ Returns status 400 if either request.body.taskName is missing or request.body.isActive is missing or some error occurs while saving task or Wbs or project. - -2. ✅ Returns status 403 if request.body.requestor is missing permission `postTask`. - -> ## Edge case diff --git a/requirements/taskController/sendReviewReq.md b/requirements/taskController/sendReviewReq.md deleted file mode 100644 index 53c03ff77..000000000 --- a/requirements/taskController/sendReviewReq.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# sendReviewReq Function - -> ## Positive case -1. ✅ Returns status 200 on successful operation. - -> ## Negative case -1. ✅ Returns status 500 if some error occurs. - -> ## Edge case diff --git a/requirements/taskController/swap.md b/requirements/taskController/swap.md deleted file mode 100644 index 93540de92..000000000 --- a/requirements/taskController/swap.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# swap Function - -> ## Positive case -1. ✅ Returns status 201 on successfully updating. - -> ## Negative case -1. ✅ Returns status 400 if either request.body.taskId1 is missing or request.body.taskId2 is missing or there is error while executing findById in Task or some error occurs while saving task. - -2. ✅ Returns status 403 if request.body.requestor is missing `swapTask` permission. - -3. ✅ Returns status 404 if some error occurs while executing find operation on Task collection. - -> ## Edge case diff --git a/requirements/taskController/updateAllParents.md b/requirements/taskController/updateAllParents.md deleted file mode 100644 index eccce4d41..000000000 --- a/requirements/taskController/updateAllParents.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateAllParents Function - -> ## Positive case -1. ✅ Returns status 200 on successful updation. - -> ## Negative case -1. ✅ Returns status 400 if some error occurs. - Not possible to check as per current structure - -> ## Edge case diff --git a/requirements/taskController/updateNum.md b/requirements/taskController/updateNum.md deleted file mode 100644 index 4b489014c..000000000 --- a/requirements/taskController/updateNum.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateNum Function - -> ## Positive case -1. ✅ Returns status 200 on successfully updating. - -> ## Negative case -1. ✅ Returns status 400 if either request.body.nums is missing or some error occurs while saving child task. - -2. ✅ Returns status 403 if request.body.requestor is missing `updateNum` permission. - -3. ✅ Returns status 404 if some error occurs while processing the child tasks. - -> ## Edge case diff --git a/requirements/taskController/updateTask.md b/requirements/taskController/updateTask.md deleted file mode 100644 index 09e631603..000000000 --- a/requirements/taskController/updateTask.md +++ /dev/null @@ -1,16 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateTask Function - -> ## Positive case -1. ✅ Returns status 201 on successfully updating. - -> ## Negative case -1. ✅ Returns status 400 if either request.body.nums is missing or some error occurs while saving child task. - -2. ✅ Returns status 403 if request.body.requestor is missing `updateTask` permission. - -3. ✅ Returns status 404 if some error occurs while executing findOneAndUpdate operation on Task collection. - -> ## Edge case diff --git a/requirements/taskController/updateTaskStatus.md b/requirements/taskController/updateTaskStatus.md deleted file mode 100644 index 59bdbdd7c..000000000 --- a/requirements/taskController/updateTaskStatus.md +++ /dev/null @@ -1,12 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# updateTaskStatus Function - -> ## Positive case -1. ✅ Returns status 201 on successful update operation. - -> ## Negative case -1. ✅ Returns status 404 if some error occurs. - -> ## Edge case diff --git a/requirements/timeZoneAPIController/getTImeZone.md b/requirements/timeZoneAPIController/getTImeZone.md deleted file mode 100644 index c7ae7801e..000000000 --- a/requirements/timeZoneAPIController/getTImeZone.md +++ /dev/null @@ -1,20 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Get Time Zone - -> ## Positive case - -1. ✅ Returns status code 200 and response data as follows: - i. current location - ii. timezone - -> ## Negative case - -1. ✅ Returns status code 403, if the user is not authorised. -2. ✅ Returns status code 401, if the API key is missing. -3. ✅ Returns status code 400, if the location is missing. -4. ✅ Returns status code 404, if geocodeAPIEndpoint returns no results. -5. ✅ Returns status code 500, if any other error occurs. - -> ## Edge case diff --git a/requirements/timeZoneAPIController/getTimeZoneProfileInitialSetup.md b/requirements/timeZoneAPIController/getTimeZoneProfileInitialSetup.md deleted file mode 100644 index 1a0f91265..000000000 --- a/requirements/timeZoneAPIController/getTimeZoneProfileInitialSetup.md +++ /dev/null @@ -1,20 +0,0 @@ -Check mark: ✅ -Cross Mark: ❌ - -# Get Time Zone - -> ## Positive case - -1. ✅ Returns status code 200 and response data as follows: - i. current location - ii. timezone - -> ## Negative case - -1. ✅ Returns status code 400, if the token is missing in the request body. -2. ✅ Returns status code 403, if the no document exists in ProfileInitialSetupToken database with requested token. -3. ✅ Returns status code 400, if the location is missing. -4. ✅ Returns status code 404, if geocodeAPIEndpoint returns no results. -5. ✅ Returns status code 500, if any other error occurs. - -> ## Edge case diff --git a/src/controllers/emailController.js b/src/controllers/emailController.js index 15243ef5b..13de952e3 100644 --- a/src/controllers/emailController.js +++ b/src/controllers/emailController.js @@ -1,87 +1,15 @@ // emailController.js const jwt = require('jsonwebtoken'); -const cheerio = require('cheerio'); const emailSender = require('../utilities/emailSender'); -const { hasPermission } = require('../utilities/permissions'); const EmailSubcriptionList = require('../models/emailSubcriptionList'); const userProfile = require('../models/userProfile'); const frontEndUrl = process.env.FRONT_END_URL || 'http://localhost:3000'; const jwtSecret = process.env.JWT_SECRET || 'EmailSecret'; -const handleContentToOC = (htmlContent) => - ` - - - - - - ${htmlContent} - - `; - -const handleContentToNonOC = (htmlContent, email) => - ` - - - - - - ${htmlContent} -

Thank you for subscribing to our email updates!

-

If you would like to unsubscribe, please click here

- - `; - -function extractImagesAndCreateAttachments(html) { - const $ = cheerio.load(html); - const attachments = []; - - $('img').each((i, img) => { - const src = $(img).attr('src'); - if (src.startsWith('data:image')) { - const base64Data = src.split(',')[1]; - const _cid = `image-${i}`; - attachments.push({ - filename: `image-${i}.png`, - content: Buffer.from(base64Data, 'base64'), - cid: _cid, - }); - $(img).attr('src', `cid:${_cid}`); - } - }); - return { - html: $.html(), - attachments, - }; -} - const sendEmail = async (req, res) => { - const canSendEmail = await hasPermission(req.body.requestor, 'sendEmails'); - if (!canSendEmail) { - res.status(403).send('You are not authorized to send emails.'); - return; - } try { const { to, subject, html } = req.body; - // Validate required fields - if (!subject || !html || !to) { - const missingFields = []; - if (!subject) missingFields.push('Subject'); - if (!html) missingFields.push('HTML content'); - if (!to) missingFields.push('Recipient email'); - console.log('missingFields', missingFields); - return res - .status(400) - .send(`${missingFields.join(' and ')} ${missingFields.length > 1 ? 'are' : 'is'} required`); - } - - // Extract images and create attachments - const { html: processedHtml, attachments } = extractImagesAndCreateAttachments(html); - - // Log recipient for debugging - console.log('Recipient:', to); - await emailSender(to, subject, html) .then(result => { @@ -93,7 +21,6 @@ const sendEmail = async (req, res) => { res.status(500).send('Error sending email'); }); - } catch (error) { console.error('Error sending email:', error); return res.status(500).send('Error sending email'); @@ -101,51 +28,48 @@ const sendEmail = async (req, res) => { }; const sendEmailToAll = async (req, res) => { - const canSendEmailToAll = await hasPermission(req.body.requestor, 'sendEmailToAll'); - if (!canSendEmailToAll) { - res.status(403).send('You are not authorized to send emails to all.'); - return; - } try { const { subject, html } = req.body; - if (!subject || !html) { - return res.status(400).send('Subject and HTML content are required'); - } - - const { html: processedHtml, attachments } = extractImagesAndCreateAttachments(html); - const users = await userProfile.find({ - firstName: '', + firstName: 'Haoji', email: { $ne: null }, isActive: true, emailSubscriptions: true, }); - if (users.length === 0) { - return res.status(404).send('No users found'); - } + let to = ''; + const emailContent = ` + + + + - const recipientEmails = users.map((user) => user.email); - console.log('# sendEmailToAll to', recipientEmails.join(',')); - if (recipientEmails.length === 0) { - throw new Error('No recipients defined'); - } - const emailContentToOCmembers = handleContentToOC(processedHtml); - await Promise.all( - recipientEmails.map((email) => - emailSender(email, subject, emailContentToOCmembers, attachments), - ), - ); + + ${html} + + `; + users.forEach((user) => { + to += `${user.email},`; + }); + emailSender(to, subject, emailContent); + const emailList = await EmailSubcriptionList.find({ email: { $ne: null } }); + emailList.forEach((emailObject) => { + const { email } = emailObject; + const emailContent = ` + + + + - const emailSubscribers = await EmailSubcriptionList.find({ email: { $exists: true, $ne: '' } }); - console.log('# sendEmailToAll emailSubscribers', emailSubscribers.length); - await Promise.all( - emailSubscribers.map(({ email }) => { - const emailContentToNonOCmembers = handleContentToNonOC(processedHtml, email); - return emailSender(email, subject, emailContentToNonOCmembers, attachments); - }), - ); + + ${html} +

Thank you for subscribing to our email updates!

+

If you would like to unsubscribe, please click here

+ + `; + emailSender(email, subject, emailContent); + }); return res.status(200).send('Email sent successfully'); } catch (error) { console.error('Error sending email:', error); @@ -183,9 +107,13 @@ const addNonHgnEmailSubscription = async (req, res) => { } const payload = { email }; - const token = jwt.sign(payload, jwtSecret, { - expiresIn: 360, - }); + const token = jwt.sign( + payload, + jwtSecret, + { + expiresIn: 360, + }, + ); const emailContent = ` diff --git a/src/controllers/emailController.spec.js b/src/controllers/emailController.spec.js deleted file mode 100644 index f5327a328..000000000 --- a/src/controllers/emailController.spec.js +++ /dev/null @@ -1,146 +0,0 @@ -const { mockReq, mockRes, assertResMock } = require('../test'); -const emailController = require('./emailController'); -const jwt = require('jsonwebtoken'); -const userProfile = require('../models/userProfile'); - - -jest.mock('jsonwebtoken'); -jest.mock('../models/userProfile'); -jest.mock('../utilities/emailSender'); - - - - -const makeSut = () => { - const { - sendEmail, - sendEmailToAll, - updateEmailSubscriptions, - addNonHgnEmailSubscription, - removeNonHgnEmailSubscription, - confirmNonHgnEmailSubscription, - } = emailController; - return { - sendEmail, - sendEmailToAll, - updateEmailSubscriptions, - addNonHgnEmailSubscription, - removeNonHgnEmailSubscription, - confirmNonHgnEmailSubscription, - }; -}; -describe('emailController Controller Unit tests', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('sendEmail function', () => { - test('should send email successfully', async () => { - const { sendEmail } = makeSut(); - const mockReq = { - body: { - to: 'recipient@example.com', - subject: 'Test Subject', - html: '

Test Body

', - }, - }; - const response = await sendEmail(mockReq, mockRes); - assertResMock(200, 'Email sent successfully', response, mockRes); - }); -}); - - describe('updateEmailSubscriptions function', () => { - test('should handle error when updating email subscriptions', async () => { - const { updateEmailSubscriptions } = makeSut(); - - - userProfile.findOneAndUpdate = jest.fn(); - - userProfile.findOneAndUpdate.mockRejectedValue(new Error('Update failed')); - - const mockReq = { - body: { - emailSubscriptions: ['subscription1', 'subscription2'], - requestor: { - email: 'test@example.com', - }, - }, - }; - - const response = await updateEmailSubscriptions(mockReq, mockRes); - - assertResMock(500, 'Error updating email subscriptions', response, mockRes); - }); - }); - - - describe('confirmNonHgnEmailSubscription function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - beforeAll(() => { - jwt.verify = jest.fn(); - }); - - test('should return 400 if token is not provided', async () => { - const { confirmNonHgnEmailSubscription } = makeSut(); - - const mockReq = { body: {} }; - const response = await confirmNonHgnEmailSubscription(mockReq, mockRes); - - assertResMock(400, 'Invalid token', response, mockRes); - }); - - test('should return 401 if token is invalid', async () => { - const { confirmNonHgnEmailSubscription } = makeSut(); - const mockReq = { body: { token: 'invalidToken' } }; - - jwt.verify.mockImplementation(() => { - throw new Error('Token is not valid'); - }); - - await confirmNonHgnEmailSubscription(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(401); - expect(mockRes.json).toHaveBeenCalledWith({ - errors: [ - { msg: 'Token is not valid' }, - ], - }); - }); - - - test('should return 400 if email is missing from payload', async () => { - const { confirmNonHgnEmailSubscription } = makeSut(); - const mockReq = { body: { token: 'validToken' } }; - - // Mocking jwt.verify to return a payload without email - jwt.verify.mockReturnValue({}); - - const response = await confirmNonHgnEmailSubscription(mockReq, mockRes); - - assertResMock(400, 'Invalid token', response, mockRes); - }); - - - - - - }); - describe('removeNonHgnEmailSubscription function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('should return 400 if email is missing', async () => { - const { removeNonHgnEmailSubscription } = makeSut(); - const mockReq = { body: {} }; - - const response = await removeNonHgnEmailSubscription(mockReq, mockRes); - - assertResMock(400, 'Email is required', response, mockRes); - }); - }); - - }); diff --git a/src/controllers/informationController.js b/src/controllers/informationController.js index 03a23ba57..792620995 100644 --- a/src/controllers/informationController.js +++ b/src/controllers/informationController.js @@ -1,17 +1,17 @@ -// const mongoose = require('mongoose'); +const mongoose = require('mongoose'); // const userProfile = require('../models/userProfile'); // const hasPermission = require('../utilities/permissions'); const escapeRegex = require('../utilities/escapeRegex'); -const cacheClosure = require('../utilities/nodeCache'); +const cache = require('../utilities/nodeCache')(); const informationController = function (Information) { - const cache = cacheClosure(); const getInformations = function (req, res) { // return all informations if cache is available if (cache.hasCache('informations')) { res.status(200).send(cache.getCache('informations')); return; } + Information.find({}, 'infoName infoContent visibility') .then((results) => { // cache results diff --git a/src/controllers/informationController.spec.js b/src/controllers/informationController.spec.js deleted file mode 100644 index e69dd2a32..000000000 --- a/src/controllers/informationController.spec.js +++ /dev/null @@ -1,392 +0,0 @@ -/* eslint-disable no-unused-vars */ -// const mongoose = require('mongoose'); -const mongoose = require('mongoose'); - -jest.mock('../utilities/nodeCache'); -const cache = require('../utilities/nodeCache'); -const Information = require('../models/information'); -const escapeRegex = require('../utilities/escapeRegex'); -const informationController = require('./informationController'); -const { mockReq, mockRes, assertResMock } = require('../test'); - -/* eslint-disable no-unused-vars */ -/* eslint-disable prefer-promise-reject-errors */ - -const makeSut = () => { - const { addInformation, getInformations, updateInformation, deleteInformation } = - informationController(Information); - - return { - addInformation, - getInformations, - updateInformation, - deleteInformation, - }; -}; -// Define flushPromises function)); -const flushPromises = () => new Promise(setImmediate); - -const makeMockCache = (method, value) => { - const cacheObject = { - getCache: jest.fn(), - removeCache: jest.fn(), - hasCache: jest.fn(), - setCache: jest.fn(), - }; - - const mockCache = jest.spyOn(cacheObject, method).mockImplementationOnce(() => value); - - cache.mockImplementationOnce(() => cacheObject); - - return { mockCache, cacheObject }; -}; - -describe('informationController module', () => { - beforeEach(() => {}); - afterEach(() => { - jest.clearAllMocks(); - }); - describe('addInformation function', () => { - test('Ensure addInformation returns 500 if any error when finding any information', async () => { - const { addInformation } = makeSut(); - const newMockReq = { - ...mockReq.body, - body: { - infoName: 'some infoName', - }, - }; - jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.reject(new Error('Error when finding'))); - const response = addInformation(newMockReq, mockRes); - await flushPromises(); - assertResMock(500, { error: new Error('Error when finding') }, response, mockRes); - }); - test('Ensure addInformation returns 400 if duplicate info Name', async () => { - const { addInformation } = makeSut(); - const data = [{ infoName: 'test Info' }]; - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.resolve(data)); - const newMockReq = { - body: { - ...mockReq.body, - infoName: 'test Info', - }, - }; - const response = addInformation(newMockReq, mockRes); - await flushPromises(); - expect(findSpy).toHaveBeenCalledWith({ - infoName: { $regex: escapeRegex(newMockReq.body.infoName), $options: 'i' }, - }); - assertResMock( - 400, - { - error: `Info Name must be unique. Another infoName with name ${newMockReq.body.infoName} already exists. Please note that info names are case insensitive`, - }, - response, - mockRes, - ); - }); - test('Ensure addInformations returns 400 if any error when saving new Information', async () => { - const { addInformation } = makeSut(); - const newMockReq = { - body: { - ...mockReq.body, - infoName: 'some Info', - }, - }; - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.resolve(true)); - jest - .spyOn(Information.prototype, 'save') - .mockImplementationOnce(() => Promise.reject(new Error('Error when saving'))); - const response = addInformation(newMockReq, mockRes); - await flushPromises(); - - expect(findSpy).toHaveBeenCalledWith({ - infoName: { $regex: escapeRegex(newMockReq.body.infoName), $options: 'i' }, - }); - assertResMock(400, new Error('Error when saving'), response, mockRes); - }); - - test('Ensure addInformation returns 201 if creating information successfully when no cache', async () => { - const { mockCache: hasCacheMock } = makeMockCache('hasCache', ''); - const { addInformation } = makeSut(); - const data = { - infoName: 'mockAdd', - infoContent: 'mockContent', - visibility: '1', - }; - - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.resolve([])); - jest.spyOn(Information.prototype, 'save').mockImplementationOnce(() => Promise.resolve(data)); - const newMockReq = { - body: { - ...mockReq.body, - infoName: 'some addInfo', - infoContent: '1', - visibility: '1', - }, - }; - const response = addInformation(newMockReq, mockRes); - await flushPromises(); - expect(findSpy).toHaveBeenCalledWith({ - infoName: { $regex: escapeRegex(newMockReq.body.infoName), $options: 'i' }, - }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - assertResMock(201, data, response, mockRes); - }); - test('Ensure addInformation returns 201 if creating information successfully', async () => { - const { mockCache: hasCacheMock, cacheObject } = makeMockCache('hasCache', '[{_id: 1}]'); - const removeCacheMock = jest - .spyOn(cacheObject, 'removeCache') - .mockImplementationOnce(() => null); - const { addInformation } = makeSut(); - const data = [ - { - infoName: 'mockAdd', - infoContent: 'mockContent', - visibility: '1', - }, - ]; - - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.resolve([])); - jest.spyOn(Information.prototype, 'save').mockImplementationOnce(() => Promise.resolve(data)); - const newMockReq = { - body: { - ...mockReq.body, - infoName: 'some addInfo', - infoContent: '1', - visibility: '1', - }, - }; - addInformation(newMockReq, mockRes); - await flushPromises(); - expect(findSpy).toHaveBeenCalledWith({ - infoName: { $regex: escapeRegex(newMockReq.body.infoName), $options: 'i' }, - }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(removeCacheMock).toHaveBeenCalledWith('informations'); - }); - }); - describe('getInformations function', () => { - test('Ensure getInformations returns 200 if when informations key in cache', async () => { - const data = [ - { - _id: 1, - infoName: 'infoName', - infoContent: 'infoContent', - visibility: '1', - }, - ]; - const { mockCache: hasCacheMock, cacheObject } = makeMockCache('hasCache', data); - const getCacheMock = jest.spyOn(cacheObject, 'getCache').mockImplementationOnce(() => data); - const { getInformations } = makeSut(); - - const response = getInformations(mockReq, mockRes); - await flushPromises(); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(getCacheMock).toHaveBeenCalledWith('informations'); - assertResMock(200, data, response, mockRes); - }); - test('Ensure getInformations returns 404 if any error when no informations key and catch error in finding', async () => { - const { mockCache: hasCacheMock } = makeMockCache('hasCache', ''); - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.reject(new Error('Error when finding information'))); - const { getInformations } = makeSut(); - - const response = getInformations(mockReq, mockRes); - await flushPromises(); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(findSpy).toHaveBeenCalledWith({}, 'infoName infoContent visibility'); - assertResMock(404, new Error('Error when finding information'), response, mockRes); - }); - - test('Ensure getInformations returns 200 when no informations key and no duplicated information', async () => { - const data = [ - { - infoName: 'mockAdd', - infoContent: 'mockContent', - visibility: '1', - }, - ]; - const { mockCache: hasCacheMock, cacheObject } = makeMockCache('hasCache', ''); - const findSpy = jest - .spyOn(Information, 'find') - .mockImplementationOnce(() => Promise.resolve(data)); - const setCacheMock = jest.spyOn(cacheObject, 'setCache').mockImplementationOnce(() => data); - - const { getInformations } = makeSut(); - const newMockReq = { - body: { - ...mockReq.body, - infoName: 'some getInfo', - infoContent: '1', - visibility: '1', - }, - }; - const response = getInformations(newMockReq, mockRes); - await flushPromises(); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(findSpy).toHaveBeenCalledWith({}, 'infoName infoContent visibility'); - expect(setCacheMock).toHaveBeenCalledWith('informations', data); - assertResMock(200, data, response, mockRes); - }); - }); - describe('deleteInformation function', () => { - test('Ensure deleteInformation returns 400 if any error when finding and delete information', async () => { - const errorMsg = 'Error when finding and deleting information by Id'; - const { deleteInformation } = makeSut(); - jest - .spyOn(Information, 'findOneAndDelete') - .mockImplementationOnce(() => Promise.reject(new Error(errorMsg))); - const response = deleteInformation(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, new Error(errorMsg), response, mockRes); - }); - test('Ensure deleteInformation returns 200 if delete information successfully no cache', async () => { - const { mockCache: hasCacheMock } = makeMockCache('hasCache', ''); - const deletedData = { - id: '601acda376045c7879d13a77', - infoName: 'deletedInfo', - infoContent: 'deleted', - visibility: '1', - }; - const { deleteInformation } = makeSut(); - const newMockReq = { - ...mockReq.body, - params: { - ...mockReq.params, - id: '601acda376045c7879d13a77', - }, - }; - const findOneDeleteSpy = jest - .spyOn(Information, 'findOneAndDelete') - .mockImplementationOnce(() => Promise.resolve(deletedData)); - const response = deleteInformation(newMockReq, mockRes); - await flushPromises(); - expect(findOneDeleteSpy).toHaveBeenCalledWith({ _id: deletedData.id }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - assertResMock(200, deletedData, response, mockRes); - }); - test('Ensure deleteInformation returns if delete information successfully and has cache', async () => { - const { mockCache: hasCacheMock, cacheObject } = makeMockCache('hasCache', '[{_id:123}]'); - const removeCacheMock = jest - .spyOn(cacheObject, 'removeCache') - .mockImplementationOnce(() => null); - const deletedData = { - id: '601acda376045c7879d13a77', - infoName: 'deletedInfo', - infoContent: 'deleted', - visibility: '1', - }; - const { deleteInformation } = makeSut(); - const newMockReq = { - ...mockReq.body, - params: { - ...mockReq.params, - id: '601acda376045c7879d13a77', - infoName: 'deletedInfo', - infoContent: 'deleted', - visibility: '1', - }, - }; - const findOneDeleteSpy = jest - .spyOn(Information, 'findOneAndDelete') - .mockImplementationOnce(() => Promise.resolve(deletedData)); - deleteInformation(newMockReq, mockRes); - await flushPromises(); - expect(findOneDeleteSpy).toHaveBeenCalledWith({ _id: deletedData.id }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(removeCacheMock).toHaveBeenCalledWith('informations'); - }); - }); - describe('updateInformation function', () => { - test('Ensure updateInformation returns 400 if any error when finding and update information', async () => { - const errorMsg = 'Error when finding and updating information by Id'; - const { updateInformation } = makeSut(); - jest - .spyOn(Information, 'findOneAndUpdate') - .mockImplementationOnce(() => Promise.reject(new Error(errorMsg))); - const response = updateInformation(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, new Error(errorMsg), response, mockRes); - }); - test('Ensure updateInformation returns 200 if finding and update information successfuly when nocache', async () => { - const { mockCache: hasCacheMock } = makeMockCache('hasCache', ''); - const data = { - id: '601acda376045c7879d13a77', - infoName: 'updatedInfo', - infoContent: 'updated', - visibility: '1', - }; - const newMockReq = { - body: { - id: '601acda376045c7879d13a77', - infoName: 'oldInfo', - infoContent: 'old', - visibility: '0', - }, - params: { - ...mockReq.params, - id: '601acda376045c7879d13a77', - }, - }; - const findOneUpdateSpy = jest - .spyOn(Information, 'findOneAndUpdate') - .mockImplementationOnce(() => Promise.resolve(data)); - const { updateInformation } = makeSut(); - const response = updateInformation(newMockReq, mockRes); - await flushPromises(); - expect(findOneUpdateSpy).toHaveBeenCalledWith({ _id: data.id }, newMockReq.body, { - new: true, - }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - assertResMock(200, data, response, mockRes); - }); - test('Ensure updateInformation returns if finding and update information successfuly when hascache', async () => { - const { mockCache: hasCacheMock, cacheObject } = makeMockCache('hasCache', '[{_id:123}]'); - const removeCacheMock = jest - .spyOn(cacheObject, 'removeCache') - .mockImplementationOnce(() => null); - const data = { - id: '601acda376045c7879d13a77', - infoName: 'updatedInfo', - infoContent: 'updated', - visibility: '1', - }; - const newMockReq = { - body: { - id: '601acda376045c7879d13a77', - infoName: 'oldInfo', - infoContent: 'old', - visibility: '0', - }, - params: { - ...mockReq.params, - id: '601acda376045c7879d13a77', - }, - }; - const findOneUpdateSpy = jest - .spyOn(Information, 'findOneAndUpdate') - .mockImplementationOnce(() => Promise.resolve(data)); - const { updateInformation } = makeSut(); - updateInformation(newMockReq, mockRes); - await flushPromises(); - expect(findOneUpdateSpy).toHaveBeenCalledWith({ _id: data.id }, newMockReq.body, { - new: true, - }); - expect(hasCacheMock).toHaveBeenCalledWith('informations'); - expect(removeCacheMock).toHaveBeenCalledWith('informations'); - }); - }); -}); diff --git a/src/controllers/jobsController.js b/src/controllers/jobsController.js deleted file mode 100644 index 65cfeafdc..000000000 --- a/src/controllers/jobsController.js +++ /dev/null @@ -1,131 +0,0 @@ -const Job = require('../models/jobs'); // Import the Job model - -// Controller to fetch all jobs with pagination, search, and filtering -const getJobs = async (req, res) => { - const { page = 1, limit = 18, search = '', category = '' } = req.query; - - try { - // Validate query parameters - const pageNumber = Math.max(1, parseInt(page, 10)); // Ensure page is at least 1 - const limitNumber = Math.max(1, parseInt(limit, 10)); // Ensure limit is at least 1 - - // Build query object - const query = {}; - if (search) query.title = { $regex: search, $options: 'i' }; // Case-insensitive search - if (category) query.category = category; - - // Fetch total count for pagination metadata - const totalJobs = await Job.countDocuments(query); - - // Fetch paginated results - const jobs = await Job.find(query) - .skip((pageNumber - 1) * limitNumber) - .limit(limitNumber); - - // Prepare response - res.json({ - jobs, - pagination: { - totalJobs, - totalPages: Math.ceil(totalJobs / limitNumber), - currentPage: pageNumber, - limit: limitNumber, - hasNextPage: pageNumber < Math.ceil(totalJobs / limitNumber), - hasPreviousPage: pageNumber > 1, - }, - }); - } catch (error) { - res.status(500).json({ error: 'Failed to fetch jobs', details: error.message }); - } -}; - -// Controller to fetch job details by ID -const getJobById = async (req, res) => { - const { id } = req.params; - - try { - const job = await Job.findById(id); - if (!job) { - return res.status(404).json({ error: 'Job not found' }); - } - res.json(job); - } catch (error) { - res.status(500).json({ error: 'Failed to fetch job', details: error.message }); - } -}; - -// Controller to create a new job -const createJob = async (req, res) => { - const { title, category, description, imageUrl, location, applyLink, jobDetailsLink } = req.body; - - try { - const newJob = new Job({ - title, - category, - description, - imageUrl, - location, - applyLink, - jobDetailsLink, - }); - - const savedJob = await newJob.save(); - res.status(201).json(savedJob); - } catch (error) { - res.status(500).json({ error: 'Failed to create job', details: error.message }); - } -}; - -// Controller to update an existing job by ID -const updateJob = async (req, res) => { - const { id } = req.params; - - try { - const updatedJob = await Job.findByIdAndUpdate(id, req.body, { new: true }); - if (!updatedJob) { - return res.status(404).json({ error: 'Job not found' }); - } - res.json(updatedJob); - } catch (error) { - res.status(500).json({ error: 'Failed to update job', details: error.message }); - } -}; - -// Controller to delete a job by ID -const deleteJob = async (req, res) => { - const { id } = req.params; - - try { - const deletedJob = await Job.findByIdAndDelete(id); - if (!deletedJob) { - return res.status(404).json({ error: 'Job not found' }); - } - res.json({ message: 'Job deleted successfully' }); - } catch (error) { - res.status(500).json({ error: 'Failed to delete job', details: error.message }); - } -}; - -const getCategories = async (req, res) => { - try { - const categories = await Job.distinct('category', {}); - - // Sort categories alphabetically - categories.sort((a, b) => a.localeCompare(b)); - - res.status(200).json({ categories }); - } catch (error) { - console.error('Error fetching categories:', error); - res.status(500).json({ message: 'Failed to fetch categories' }); - } -}; - -// Export controllers as a plain object -module.exports = { - getJobs, - getJobById, - createJob, - updateJob, - deleteJob, - getCategories, -}; diff --git a/src/controllers/logincontroller.js b/src/controllers/logincontroller.js index 794d00d70..3ba0203aa 100644 --- a/src/controllers/logincontroller.js +++ b/src/controllers/logincontroller.js @@ -63,7 +63,7 @@ const logincontroller = function () { res.status(200).send({ token }); } else { - res.status(404).send({ + res.status(403).send({ message: 'Invalid password.', }); } diff --git a/src/controllers/logincontroller.spec.js b/src/controllers/logincontroller.spec.js index 995be69de..595bfe77b 100644 --- a/src/controllers/logincontroller.spec.js +++ b/src/controllers/logincontroller.spec.js @@ -110,7 +110,7 @@ describe('logincontroller module', () => { expect(findOneSpy).toHaveBeenCalledWith({ email: mockReqModified.body.email }); assertResMock( - 404, + 403, { message: 'Invalid password.', }, diff --git a/src/controllers/ownerMessageController.js b/src/controllers/ownerMessageController.js index 3f74cb112..1b2c30205 100644 --- a/src/controllers/ownerMessageController.js +++ b/src/controllers/ownerMessageController.js @@ -1,11 +1,8 @@ -const helper = require('../utilities/permissions'); - const ownerMessageController = function (OwnerMessage) { const getOwnerMessage = async function (req, res) { try { const results = await OwnerMessage.find({}); - if (results.length === 0) { - // first time initialization + if (results.length === 0) { // first time initialization const ownerMessage = new OwnerMessage(); await ownerMessage.save(); res.status(200).send({ ownerMessage }); @@ -18,9 +15,7 @@ const ownerMessageController = function (OwnerMessage) { }; const updateOwnerMessage = async function (req, res) { - if ( - !(await helper.hasPermission(req.body.requestor, 'editHeaderMessage')) - ) { + if (req.body.requestor.role !== 'Owner') { res.status(403).send('You are not authorized to create messages!'); } const { isStandard, newMessage } = req.body; @@ -45,9 +40,7 @@ const ownerMessageController = function (OwnerMessage) { }; const deleteOwnerMessage = async function (req, res) { - if ( - !(await helper.hasPermission(req.body.requestor, 'editHeaderMessage')) - ) { + if (req.body.requestor.role !== 'Owner') { res.status(403).send('You are not authorized to delete messages!'); } try { diff --git a/src/controllers/popupEditorController.spec.js b/src/controllers/popupEditorController.spec.js deleted file mode 100644 index e70b05553..000000000 --- a/src/controllers/popupEditorController.spec.js +++ /dev/null @@ -1,163 +0,0 @@ -const PopUpEditor = require('../models/popupEditor'); -const { mockReq, mockRes, assertResMock } = require('../test'); - -jest.mock('../utilities/permissions'); - -const helper = require('../utilities/permissions'); -const popupEditorController = require('./popupEditorController'); - -const flushPromises = () => new Promise(setImmediate); - -const mockHasPermission = (value) => - jest.spyOn(helper, 'hasPermission').mockImplementationOnce(() => Promise.resolve(value)); - -const makeSut = () => { - const { getAllPopupEditors, getPopupEditorById, createPopupEditor, updatePopupEditor } = - popupEditorController(PopUpEditor); - return { getAllPopupEditors, getPopupEditorById, createPopupEditor, updatePopupEditor }; -}; - -describe('popupEditorController Controller Unit tests', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - describe(`getAllPopupEditors function`, () => { - test(`Should return 200 and popup editors on success`, async () => { - const { getAllPopupEditors } = makeSut(); - const mockPopupEditors = [{ popupName: 'popup', popupContent: 'content' }]; - jest.spyOn(PopUpEditor, 'find').mockResolvedValue(mockPopupEditors); - const response = await getAllPopupEditors(mockReq, mockRes); - assertResMock(200, mockPopupEditors, response, mockRes); - }); - - test(`Should return 404 on error`, async () => { - const { getAllPopupEditors } = makeSut(); - const error = new Error('Test Error'); - - jest.spyOn(PopUpEditor, 'find').mockRejectedValue(error); - const response = await getAllPopupEditors(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - }); - }); - - describe(`getPopupEditorById function`, () => { - test(`Should return 200 and popup editor on success`, async () => { - const { getPopupEditorById } = makeSut(); - const mockPopupEditor = { popupName: 'popup', popupContent: 'content' }; - jest.spyOn(PopUpEditor, 'findById').mockResolvedValue(mockPopupEditor); - const response = await getPopupEditorById(mockReq, mockRes); - assertResMock(200, mockPopupEditor, response, mockRes); - }); - - test(`Should return 404 on error`, async () => { - const { getPopupEditorById } = makeSut(); - const error = new Error('Test Error'); - - jest.spyOn(PopUpEditor, 'findById').mockRejectedValue(error); - const response = await getPopupEditorById(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - }); - }); - - describe(`createPopupEditor function`, () => { - test(`Should return 403 if user is not authorized`, async () => { - const { createPopupEditor } = makeSut(); - mockHasPermission(false); - const response = await createPopupEditor(mockReq, mockRes); - assertResMock( - 403, - { error: 'You are not authorized to create new popup' }, - response, - mockRes, - ); - }); - - test(`Should return 400 if popupName or popupContent is missing`, async () => { - const { createPopupEditor } = makeSut(); - mockHasPermission(true); - const response = await createPopupEditor(mockReq, mockRes); - assertResMock( - 400, - { error: 'popupName , popupContent are mandatory fields' }, - response, - mockRes, - ); - }); - - test(`Should return 201 and popup editor on success`, async () => { - const { createPopupEditor } = makeSut(); - mockHasPermission(true); - mockReq.body = { popupName: 'popup', popupContent: 'content' }; - const mockPopupEditor = { save: jest.fn().mockResolvedValue(mockReq.body) }; - jest.spyOn(PopUpEditor.prototype, 'save').mockImplementationOnce(mockPopupEditor.save); - const response = await createPopupEditor(mockReq, mockRes); - expect(mockPopupEditor.save).toHaveBeenCalled(); - assertResMock(201, mockReq.body, response, mockRes); - }); - - test(`Should return 500 on error`, async () => { - const { createPopupEditor } = makeSut(); - mockHasPermission(true); - const error = new Error('Test Error'); - - jest.spyOn(PopUpEditor.prototype, 'save').mockRejectedValue(error); - const response = await createPopupEditor(mockReq, mockRes); - await flushPromises(); - - assertResMock(500, { error }, response, mockRes); - }); - }); - describe(`updatePopupEditor function`, () => { - test(`Should return 403 if user is not authorized`, async () => { - const { updatePopupEditor } = makeSut(); - mockHasPermission(false); - const response = await updatePopupEditor(mockReq, mockRes); - assertResMock( - 403, - { error: 'You are not authorized to create new popup' }, - response, - mockRes, - ); - }); - - test(`Should return 400 if popupContent is missing`, async () => { - const { updatePopupEditor } = makeSut(); - mockReq.body = {}; - mockHasPermission(true); - const response = await updatePopupEditor(mockReq, mockRes); - assertResMock(400, { error: 'popupContent is mandatory field' }, response, mockRes); - }); - - test(`Should return 201 and popup editor on success`, async () => { - const { updatePopupEditor } = makeSut(); - mockHasPermission(true); - mockReq.body = { popupContent: 'content' }; - const mockPopupEditor = { save: jest.fn().mockResolvedValue(mockReq.body) }; - jest.spyOn(PopUpEditor, 'findById').mockImplementationOnce((mockReq, callback) => callback(null, mockPopupEditor)); - jest.spyOn(PopUpEditor.prototype, 'save').mockImplementationOnce(mockPopupEditor.save); - const response = await updatePopupEditor(mockReq, mockRes); - expect(mockPopupEditor.save).toHaveBeenCalled(); - assertResMock(201, mockReq.body, response, mockRes); - }); - - test('Should return 500 on popupEditor save error', async () => { - const { updatePopupEditor } = makeSut(); - mockHasPermission(true); - const err = new Error('Test Error'); - mockReq.body = { popupContent: 'content' }; - const mockPopupEditor = { save: jest.fn().mockRejectedValue(err)}; - jest - .spyOn(PopUpEditor, 'findById') - .mockImplementation((mockReq, callback) => callback(null, mockPopupEditor)); - jest.spyOn(PopUpEditor.prototype, 'save').mockImplementationOnce(mockPopupEditor.save); - const response = await updatePopupEditor(mockReq, mockRes); - await flushPromises(); - assertResMock(500, {err}, response, mockRes); - }); - }); -}); diff --git a/src/controllers/profileInitialSetupController.js b/src/controllers/profileInitialSetupController.js index e56e7e406..fcf24ce1a 100644 --- a/src/controllers/profileInitialSetupController.js +++ b/src/controllers/profileInitialSetupController.js @@ -172,22 +172,13 @@ const profileInitialSetupController = function ( const link = `${baseUrl}/ProfileInitialSetup/${savedToken.token}`; await session.commitTransaction(); - // Send response immediately without waiting for email acknowledgment - res.status(200).send({ message: 'Token created successfully, email is being sent.' }); - - // Asynchronously send the email acknowledgment - setImmediate(async () => { - try { - await sendEmailWithAcknowledgment( - email, - 'NEEDED: Complete your One Community profile setup', - sendLinkMessage(link), - ); - } catch (emailError) { - // Log email sending failure - LOGGER.logException(emailError, 'sendEmailWithAcknowledgment', JSON.stringify({ email, link }), null); - } - }); + const acknowledgment = await sendEmailWithAcknowledgment( + email, + 'NEEDED: Complete your One Community profile setup', + sendLinkMessage(link), + ); + + return res.status(200).send(acknowledgment); } catch (error) { await session.abortTransaction(); LOGGER.logException(error, 'getSetupToken', JSON.stringify(req.body), null); @@ -532,26 +523,30 @@ const profileInitialSetupController = function ( */ const getSetupInvitation = (req, res) => { const { role } = req.body.requestor; - - const { permissions } = req.body.requestor; - let user_permissions = ['getUserProfiles','postUserProfile','putUserProfile','changeUserStatus'] - if ((role === 'Administrator') || (role === 'Owner') || (role === 'Manager') || (role === 'Mentor') || user_permissions.some(e=>permissions.frontPermissions.includes(e))) { - try{ - ProfileInitialSetupToken - .find({ isSetupCompleted: false }) - .sort({ createdDate: -1 }) - .exec((err, result) => { - // Handle the result - if (err) { - LOGGER.logException(err); - return res.status(500).send('Internal Error: Please retry. If the problem persists, please contact the administrator'); - } - return res.status(200).send(result); - }); - } catch (error) { - LOGGER.logException(error); - return res.status(500).send('Internal Error: Please retry. If the problem persists, please contact the administrator'); - } + if (role === 'Administrator' || role === 'Owner') { + try { + ProfileInitialSetupToken.find({ isSetupCompleted: false }) + .sort({ createdDate: -1 }) + .exec((err, result) => { + // Handle the result + if (err) { + LOGGER.logException(err); + return res + .status(500) + .send( + 'Internal Error: Please retry. If the problem persists, please contact the administrator', + ); + } + return res.status(200).send(result); + }); + } catch (error) { + LOGGER.logException(error); + return res + .status(500) + .send( + 'Internal Error: Please retry. If the problem persists, please contact the administrator', + ); + } } else { return res.status(403).send('You are not authorized to get setup history.'); } diff --git a/src/controllers/projectController.js b/src/controllers/projectController.js index 6d3cfc37b..72522ba80 100644 --- a/src/controllers/projectController.js +++ b/src/controllers/projectController.js @@ -291,17 +291,8 @@ const projectController = function (Project) { res.status(400).send('Invalid request'); return; } + const getId = await hasPermission(req.body.requestor, 'getProjectMembers'); - const getProjMembers = await hasPermission(req.body.requestor, 'getProjectMembers'); - - // If a user has permission to post, edit, or suggest tasks, they also have the ability to assign resources to those tasks. - // Therefore, the _id field must be included when retrieving the user profile for project members (resources). - const postTask = await hasPermission(req.body.requestor, 'postTask'); - const updateTask = await hasPermission(req.body.requestor, 'updateTask'); - const suggestTask = await hasPermission(req.body.requestor, 'suggestTask'); - - const getId = (getProjMembers || postTask || updateTask || suggestTask); - userProfile .find( { projects: projectId }, diff --git a/src/controllers/reasonSchedulingController.spec.js b/src/controllers/reasonSchedulingController.spec.js deleted file mode 100644 index e58c77466..000000000 --- a/src/controllers/reasonSchedulingController.spec.js +++ /dev/null @@ -1,626 +0,0 @@ -const moment = require('moment-timezone'); -const { mockReq, mockRes, mockUser } = require('../test'); -const UserModel = require('../models/userProfile'); - -jest.mock('../utilities/emailSender'); -const emailSender = require('../utilities/emailSender') - -const { - postReason, - getAllReasons, - getSingleReason, - patchReason, - deleteReason, -} = require('./reasonSchedulingController'); - -// assertResMock -const ReasonModel = require('../models/reason'); - -const flushPromises = () => new Promise(setImmediate); - -function mockDay(dayIdx, past = false) { - const date = moment().tz('America/Los_Angeles').startOf('day'); - while (date.day() !== dayIdx) { - date.add(past ? -1 : 1, 'days'); - } - return date; -} - -describe('reasonScheduling Controller', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockRes.json = jest.fn(); - mockReq.body = { - ...mockReq.body, - ...mockUser(), - reasonData: { - date: mockDay(0), - message: 'some reason', - }, - currentDate: moment.tz('America/Los_Angeles').startOf('day'), - }; - mockReq.params = { - ...mockReq.params, - ...mockUser(), - }; - }); - afterEach(() => { - jest.clearAllMocks(); - }); - describe('postReason method', () => { - test('Ensure postReason returns 400 for warning to choose Sunday', async () => { - mockReq.body.reasonData.date = mockDay(1, true); - await postReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: - "You must choose the Sunday YOU'LL RETURN as your date. This is so your reason ends up as a note on that blue square.", - errorCode: 0, - }), - ); - }); - test('Ensure postReason returns 400 for warning to choose a future date', async () => { - mockReq.body.reasonData.date = mockDay(0, true); - await postReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'You should select a date that is yet to come', - errorCode: 7, - }), - ); - }); - test('Ensure postReason returns 400 for not providing reason', async () => { - mockReq.body.reasonData.message = null; - await postReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'You must provide a reason.', - errorCode: 6, - }), - ); - }); - test('Ensure postReason returns 404 when error in finding user Id', async () => { - const mockFindUser = jest.spyOn(UserModel, 'findById').mockImplementationOnce(() => - Promise.resolve(null)); - - await postReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.body.userId); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'User not found', - errorCode: 2, - }), - ); - }); - test('Ensure postReason returns 403 when duplicate reason to the date', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - jest.spyOn(UserModel, 'findOneAndUpdate').mockResolvedValueOnce({ - _id: mockReq.body.userId, - timeOffFrom: mockReq.body.currentDate, - timeOffTill: mockReq.body.reasonData.date, - }); - const mockReason = { - reason: 'Some Reason', - userId: mockReq.body.userId, - date: moment.tz('America/Los_Angeles').startOf('day').toISOString(), - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValue(mockReason); - - await postReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(403); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.body.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.body.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'The reason must be unique to the date', - errorCode: 3, - }), - ); - }); - test('Ensure postReason returns 400 when any error in saving.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - jest.spyOn(UserModel, 'findOneAndUpdate').mockResolvedValueOnce({ - _id: mockReq.body.userId, - timeOffFrom: mockReq.body.currentDate, - timeOffTill: mockReq.body.reasonData.date, - }); - mockRes.sendStatus = jest.fn().mockReturnThis(); - const newReason = { - reason: mockReq.body.reasonData.message, - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.body.userId, - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValue(); - const mockSave = jest.spyOn(ReasonModel.prototype, 'save').mockRejectedValue(newReason); - emailSender.mockImplementation(() => { - throw new Error('Failed to send email'); - }); - - await postReason(mockReq, mockRes); - await flushPromises(); - emailSender.mockRejectedValue(new Error('Failed')); - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.body.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.body.userId, - }); - expect(mockSave).toHaveBeenCalledWith(); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - errMessage: 'Something went wrong', - }), - ); - }); - test('Ensure postReason returns 200 if schedule reason and send blue sqaure email successfully', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - jest.spyOn(UserModel, 'findOneAndUpdate').mockResolvedValueOnce({ - _id: mockReq.body.userId, - timeOffFrom: mockReq.body.currentDate, - timeOffTill: mockReq.body.reasonData.date, - }); - mockRes.sendStatus = jest.fn().mockReturnThis(); - const newReason = { - reason: mockReq.body.reasonData.message, - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.body.userId, - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValue(); - const mockSave = jest.spyOn(ReasonModel.prototype, 'save').mockResolvedValue(newReason); - emailSender.mockImplementation(() => { - Promise.resolve(); - }); - await postReason(mockReq, mockRes); - await flushPromises(); - expect(mockRes.sendStatus).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.body.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.body.userId, - }); - expect(mockSave).toHaveBeenCalledWith(); - }); - }); - describe('getAllReason method', () => { - test('Ensure get AllReason returns 404 when error in finding user Id', async () => { - const mockFindUser = jest.spyOn(UserModel, 'findById').mockImplementationOnce(() => null); - - await getAllReasons(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'User not found', - }), - ); - }); - test('Ensure get AllReason returns 400 when any error in fetching the user', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const mockFoundReason = jest.spyOn(ReasonModel, 'find').mockRejectedValueOnce(null); - await getAllReasons(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - userId: mockReq.params.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - errMessage: 'Something went wrong while fetching the user', - }), - ); - }); - test('Ensure get AllReason returns 200 when get schedule reason successfully', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const reasons = { - reason: 'Some Reason', - userId: mockReq.params.userId, - date: moment.tz('America/Los_Angeles').startOf('day').toISOString(), - isSet: true, - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'find').mockResolvedValue(reasons); - await getAllReasons(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - userId: mockReq.params.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - reasons, - }), - ); - }); - }); - describe('getSingleReason method', () => { - test('Ensure getSingleReason return 400 when any error in fetching the user', async () => { - await getSingleReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Something went wrong while fetching single reason', - }), - ); - }); - test('Ensure getSingleReason return 404 when any error in find user by Id', async () => { - mockReq.query = { - queryData: mockDay(0), - }; - const mockFindUser = jest.spyOn(UserModel, 'findById').mockImplementationOnce(() => null); - - await getSingleReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'User not found', - errorCode: 2, - }), - ); - }); - test('Ensure getSingleReason return 200 if not found schedule reason and return empty object successfully.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - - mockReq.query = { - queryDate: mockDay(0), - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValueOnce(); - - await getSingleReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.query.queryDate, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith({ - reason: '', - date: '', - userId: '', - isSet: false, - }); - }); - test('Ensure getSingleReason return 200 if found schedule reason and return reason successfully.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - - mockReq.query = { - queryDate: mockDay(0), - }; - const singleReason = { - reason: 'Some Reason', - userId: mockReq.params.userId, - date: moment.tz('America/Los_Angeles').startOf('day').toISOString(), - isSet: true, - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValue(singleReason); - - await getSingleReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.query.queryDate, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith(singleReason); - }); - }); - describe('patchReason method', () => { - test('Ensure patchReason returns 400 for not providing reason', async () => { - mockReq.body.reasonData.message = null; - await patchReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'You must provide a reason.', - errorCode: 6, - }), - ); - }); - test('Ensure patchReason returns 404 when error in finding user Id', async () => { - const mockFindUser = jest.spyOn(UserModel, 'findById').mockImplementationOnce(() => null); - - await patchReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'User not found', - errorCode: 2, - }), - ); - }); - test('Ensure patchReason returns 404 when error in finding reason', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValueOnce(); - await patchReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Reason not found', - errorCode: 4, - }), - ); - }); - test('Ensure patchReason returns 400 when any error in saving.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const oldReason = { - reason: 'old message', - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - save: jest.fn().mockRejectedValueOnce(), - }; - emailSender.mockImplementation(() => { - throw new Error('Failed to send email'); - }); - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValueOnce(oldReason); - await patchReason(mockReq, mockRes); - await flushPromises(); - emailSender.mockRejectedValue(new Error('Failed')); - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - }); - expect(oldReason.save).toHaveBeenCalledWith(); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'something went wrong while patching the reason', - }), - ); - }); - test('Ensure patchReason returns 200 when any error in saving.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const oldReason = { - reason: mockReq.body.reasonData.message, - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - save: jest.fn().mockResolvedValueOnce(), - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValueOnce(oldReason); - emailSender.mockImplementation(() => { - Promise.resolve(); - }); - await patchReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - }); - expect(oldReason.save).toHaveBeenCalledWith(); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Reason Updated!', - }), - ); - }); - }); - describe('deleteReason method', () => { - test('Ensure deleteReason return 403 when no permission to delete', async () => { - const newMockReq = { - ...mockReq, - body: { - ...mockReq.body, - ...mockReq.requestor, - requestor: { - role: 'Volunteer', - }, - }, - }; - await deleteReason(newMockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(403); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'You must be an Owner or Administrator to schedule a reason for a Blue Square', - - errorCode: 1, - }), - ); - }); - test('Ensure deleteReason return 404 when not finding user by ID', async () => { - const mockFindUser = jest.spyOn(UserModel, 'findById').mockImplementationOnce(() => null); - await deleteReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'User not found', - errorCode: 2, - }), - ); - }); - test('Ensure deleteReason returns 404 when error in finding reason', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockResolvedValueOnce(); - await deleteReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(404); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Reason not found', - errorCode: 4, - }), - ); - }); - test('Ensure deleteReason returns 500 when error in removing reason', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const foundReason = { - reason: mockReq.body.reasonData.message, - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - remove: jest.fn((cb) => cb(true)), - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockReturnValueOnce(foundReason); - - await deleteReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(500); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Error while deleting document', - errorCode: 5, - }), - ); - }); - test('Ensure deleteReason returns 200 if delete reason successfully.', async () => { - const mockFindUser = jest - .spyOn(UserModel, 'findById') - .mockImplementationOnce(() => mockUser()); - const foundReason = { - reason: mockReq.body.reasonData.message, - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - userId: mockReq.params.userId, - remove: jest.fn((cb) => cb(false)), - }; - const mockFoundReason = jest.spyOn(ReasonModel, 'findOne').mockReturnValueOnce(foundReason); - - await deleteReason(mockReq, mockRes); - await flushPromises(); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockFindUser).toHaveBeenCalledWith(mockReq.params.userId); - expect(mockFoundReason).toHaveBeenCalledWith({ - date: moment - .tz(mockReq.body.reasonData.date, 'America/Los_Angeles') - .startOf('day') - .toISOString(), - }); - expect(mockRes.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Document deleted', - }), - ); - }); - }); -}); diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index b4d71a5dd..1e019ffa1 100644 --- a/src/controllers/taskController.js +++ b/src/controllers/taskController.js @@ -683,10 +683,7 @@ const taskController = function (Task) { }; const updateTask = async (req, res) => { - if ( - !(await hasPermission(req.body.requestor, 'updateTask')) && - !(await hasPermission(req.body.requestor, 'removeUserFromTask')) - ) { + if (!(await hasPermission(req.body.requestor, 'updateTask'))) { res.status(403).send({ error: 'You are not authorized to update Task.' }); return; } diff --git a/src/controllers/taskController.spec.js b/src/controllers/taskController.spec.js deleted file mode 100644 index 7d1196df7..000000000 --- a/src/controllers/taskController.spec.js +++ /dev/null @@ -1,1555 +0,0 @@ -const mongoose = require('mongoose'); - -// Utility to aid in testing -jest.mock('../utilities/permissions', () => ({ - hasPermission: jest.fn(), -})); - -jest.mock('../utilities/emailSender', () => jest.fn()); - -const taskHelperMethods = { - getTasksForTeams: jest.fn(), - getTasksForSingleUser: jest.fn(), -}; -jest.mock('../helpers/taskHelper', () => () => ({ ...taskHelperMethods })); - -const flushPromises = () => new Promise(setImmediate); -const { mockReq, mockRes, assertResMock } = require('../test'); -const { hasPermission } = require('../utilities/permissions'); -const emailSender = require('../utilities/emailSender'); - -// controller to test -const taskController = require('./taskController'); - -// MongoDB Model imports -const Task = require('../models/task'); -const Project = require('../models/project'); -const UserProfile = require('../models/userProfile'); -const WBS = require('../models/wbs'); -const FollowUp = require('../models/followUp'); - -const makeSut = () => { - const { - getTasks, - getWBSId, - importTask, - postTask, - updateNum, - moveTask, - deleteTask, - deleteTaskByWBS, - updateTask, - swap, - getTaskById, - fixTasks, - updateAllParents, - getTasksByUserId, - sendReviewReq, - getTasksForTeamsByUser, - updateTaskStatus, - } = taskController(Task); - - return { - getTasks, - getWBSId, - importTask, - postTask, - updateNum, - moveTask, - deleteTask, - deleteTaskByWBS, - updateTask, - swap, - getTaskById, - fixTasks, - updateAllParents, - getTasksByUserId, - sendReviewReq, - getTasksForTeamsByUser, - updateTaskStatus, - }; -}; - -describe('Unit Tests for taskController.js', () => { - afterAll(() => { - jest.resetAllMocks(); - }); - - describe('getTasks function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Returns 200 on successfully querying the document', async () => { - const { getTasks } = makeSut(); - const mockData = 'some random data'; - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce(mockData); - - const response = await getTasks(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, mockData, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalledTimes(1); - }); - - test('Returns 200 on successfully querying the document', async () => { - const { getTasks } = makeSut(); - const error = 'some random error'; - - const taskFindSpy = jest.spyOn(Task, 'find').mockRejectedValueOnce(error); - - const response = await getTasks(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('getWBSId function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Returns 200 on successfully querying the document', async () => { - const { getWBSId } = makeSut(); - const mockData = 'some random data'; - - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValueOnce(mockData); - - const response = await getWBSId(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, mockData, response, mockRes); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalledTimes(1); - }); - - test('Returns 200 on successfully querying the document', async () => { - const { getWBSId } = makeSut(); - const error = 'some random error'; - - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockRejectedValueOnce(error); - - const response = await getWBSId(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('importTasks function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `importTask` permission is missing', async () => { - const { importTask } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to create new Task.' }; - - const response = await importTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 201 on successful import operation', async () => { - const { importTask } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.params.wbs = 'wbs123'; - mockReq.body.list = [ - { - _id: 'mongoDB-Id', - num: '1', - level: 1, - parentId1: null, - parentId2: null, - parentId3: null, - mother: null, - resources: ['parth|userId123|parthProfilePic', 'test|test123|testProfilePic'], - }, - ]; - - const saveMock = jest - .fn() - .mockImplementation(() => Promise.resolve({ _id: '1', wbsId: 'wbs123' })); - const TaskConstructorSpy = jest.spyOn(Task.prototype, 'save').mockImplementation(saveMock); - - const data = 'done'; - - const response = await importTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(201, data, response, mockRes); - expect(TaskConstructorSpy).toBeCalled(); - }); - - test('Return 400 on encountering any error while saving task', async () => { - const { importTask } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.params.wbs = 'wbs123'; - mockReq.body.list = [ - { - _id: 'mongoDB-Id', - num: '1', - level: 1, - parentId1: null, - parentId2: null, - parentId3: null, - mother: null, - resources: ['parth|userId123|parthProfilePic', 'test|test123|testProfilePic'], - }, - ]; - - const error = new Error('error while saving'); - - const saveMock = jest.fn().mockImplementation(() => Promise.reject(error)); - const TaskConstructorSpy = jest.spyOn(Task.prototype, 'save').mockImplementation(saveMock); - - const response = await importTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(TaskConstructorSpy).toBeCalled(); - }); - }); - - describe('postTask function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `postTask` permission is missing', async () => { - const { postTask } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to create new Task.' }; - - const response = await postTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test.each([ - [ - { taskName: undefined, isActive: true }, - 'Task Name, Active status, Task Number are mandatory fields', - ], - [ - { taskName: 'some task name', isActive: undefined }, - 'Task Name, Active status, Task Number are mandatory fields', - ], - [ - { taskName: undefined, isActive: undefined }, - 'Task Name, Active status, Task Number are mandatory fields', - ], - ])('Return 400 if any required field is missing', async (body, expectedError) => { - const { postTask } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - // Set the request body based on the current test case - mockReq.body.taskName = body.taskName; - mockReq.body.isActive = body.isActive; - - const error = { error: expectedError }; - - const response = await postTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 201 on successfully saving a new Task', async () => { - const { postTask } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - const newTask = { - taskName: 'Sample Task', - wbsId: new mongoose.Types.ObjectId(), - num: '1', - level: 1, - position: 1, - childrenQty: 0, - isActive: true, - }; - - // Mock the current datetime - const currentDate = Date.now(); - - // Mock Task model - const mockTask = { - save: jest.fn().mockResolvedValue({ - _id: new mongoose.Types.ObjectId(), - wbsId: new mongoose.Types.ObjectId(), - createdDatetime: currentDate, - modifiedDatetime: currentDate, - }), - }; - const taskSaveSpy = jest.spyOn(Task.prototype, 'save').mockResolvedValue(mockTask); - - // Mock WBS model - const mockWBS = { - _id: new mongoose.Types.ObjectId(), - projectId: 'projectId', - modifiedDatetime: Date.now(), - save: jest.fn().mockResolvedValue({ - _id: new mongoose.Types.ObjectId(), - projectId: 'projectId', - modifiedDatetime: Date.now(), - }), - }; - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockWBS); - - // Mock Project model - const mockProjectObj = { - save: jest.fn().mockResolvedValue({ - _id: new mongoose.Types.ObjectId(), - modifiedDatetime: currentDate, - }), - modifiedDatetime: currentDate, - }; - const projectFindByIdSpy = jest.spyOn(Project, 'findById').mockResolvedValue(mockProjectObj); - - // add the necessary request params - mockReq.params = { - ...mockReq.params, - id: new mongoose.Types.ObjectId(), - }; - - // add the necessary body parameters - mockReq.body = { - ...mockReq.body, - ...newTask, - }; - - const response = await postTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(201, expect.anything(), response, mockRes); - expect(taskSaveSpy).toBeCalled(); - expect(wbsFindByIdSpy).toBeCalled(); - expect(projectFindByIdSpy).toBeCalled(); - }); - - test('Return 400 on encountering any error during Promise.all', async () => { - const { postTask } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - const newTask = { - taskName: 'Sample Task', - wbsId: new mongoose.Types.ObjectId(), - num: '1', - level: 1, - position: 1, - childrenQty: 0, - isActive: true, - }; - - // Mock the current datetime - const currentDate = Date.now(); - - // Mock the Task model - const mockTaskError = new Error('Failed to save task'); - - // Use jest.fn() to mock the save method to reject with an error - const taskSaveMock = jest.fn().mockRejectedValue(mockTaskError); - - // Spy on the Task prototype's save method - const taskSaveSpy = jest.spyOn(Task.prototype, 'save').mockImplementation(taskSaveMock); - - // Mock WBS model - const mockWBS = { - _id: new mongoose.Types.ObjectId(), - projectId: 'projectId', - modifiedDatetime: Date.now(), - save: jest.fn().mockResolvedValue({ - _id: new mongoose.Types.ObjectId(), - projectId: 'projectId', - modifiedDatetime: Date.now(), - }), - }; - // Mock `WBS.findById` to return `mockWBS` - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockWBS); - - // Mock Project model - const mockProjectObj = { - save: jest.fn().mockResolvedValueOnce({ - _id: new mongoose.Types.ObjectId(), - modifiedDatetime: currentDate, - }), - modifiedDatetime: currentDate, - }; - const projectFindByIdSpy = jest - .spyOn(Project, 'findById') - .mockResolvedValueOnce(mockProjectObj); - - // add the necessary request params - mockReq.params = { - ...mockReq.params, - id: new mongoose.Types.ObjectId(), - }; - - // add the necessary body parameters - mockReq.body = { - ...mockReq.body, - ...newTask, - }; - - const response = await postTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, mockTaskError, response, mockRes); - expect(taskSaveSpy).toBeCalled(); - expect(wbsFindByIdSpy).toBeCalled(); - expect(projectFindByIdSpy).toBeCalled(); - }); - }); - - describe('updateNum function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `updateNum` permission is missing', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to create new projects.' }; - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 400 if `nums` is missing from the request body', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - const error = { error: 'Num is a mandatory fields' }; - mockReq.body.nums = null; - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 200 on successful update - nums is empty array', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.nums = []; - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, true, response, mockRes); - }); - - test('Return 200 on successful update - nums is not an empty array', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.nums = [ - { - id: 'sample-id', - num: 'sample-num', - }, - ]; - - const mockDataForTaskFindByIdSpy = { - num: 0, - save: jest.fn().mockResolvedValue({}), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - callback(null, mockDataForTaskFindByIdSpy); - }); - - const mockDataForTaskFindSpy = []; - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce(mockDataForTaskFindSpy); - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, true, response, mockRes); - expect(taskFindSpy).toBeCalled(); - expect(taskFindByIdSpy).toBeCalled(); - }); - - test('Return 404 if error occurs on Task.find()', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.nums = [ - { - id: 'sample-id', - num: 'sample-num', - }, - ]; - - const mockDataForTaskFindByIdSpy = { - num: 0, - save: jest.fn().mockResolvedValue({}), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - callback(null, mockDataForTaskFindByIdSpy); - }); - - const mockError = new Error({ error: 'some error occurred' }); - const taskFindSpy = jest.spyOn(Task, 'find').mockRejectedValueOnce(mockError); - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, mockError, response, mockRes); - expect(taskFindSpy).toBeCalled(); - expect(taskFindByIdSpy).toBeCalled(); - }); - - test('Return 400 if error occurs while saving a Task within Task.findById()', async () => { - const { updateNum } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.nums = [ - { - id: 'sample-id', - num: 'sample-num', - }, - ]; - - const mockError = new Error({ error: 'some error occurred' }); - - const mockDataForTaskFindByIdSpy = { - num: 0, - save: jest.fn().mockRejectedValueOnce(mockError), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - callback(null, mockDataForTaskFindByIdSpy); - }); - - const mockDataForTaskFindSpy = []; - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce(mockDataForTaskFindSpy); - - const response = await updateNum(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, mockError, response, mockRes); - expect(taskFindSpy).toBeCalled(); - expect(taskFindByIdSpy).toBeCalled(); - }); - }); - - describe('moveTask function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 400 if either `fromNum` or `toNum` is missing in request body', async () => { - const { moveTask } = makeSut(); - - const error = { error: 'wbsId, fromNum, toNum are mandatory fields' }; - mockReq.body.fromNum = null; - - const response = await moveTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 200 on successful exeecution', async () => { - const { moveTask } = makeSut(); - - const requestData = { - body: { - fromNum: '1.0', - toNum: '2.0', - }, - }; - - mockReq.body = { - ...mockReq.body, - ...requestData.body, - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValue([ - { num: '1.0', save: jest.fn().mockResolvedValue({}) }, - { num: '1.1', save: jest.fn().mockResolvedValue({}) }, - ]); - - mockReq.params.wbsId = 'someWbsId'; - - const response = await moveTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, 'Success!', response, mockRes); - expect(taskFindSpy).toBeCalled(); - }); - - test('Return 400 on some error', async () => { - const { moveTask } = makeSut(); - - const requestData = { - body: { - fromNum: '1.0', - toNum: '2.0', - }, - }; - - mockReq.body = { - ...mockReq.body, - ...requestData.body, - }; - - const error = new Error({ error: 'some error' }); - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValue([ - { num: '1.0', save: jest.fn().mockResolvedValue({}) }, - { num: '1.1', save: jest.fn().mockRejectedValueOnce(error) }, - ]); - - mockReq.params.wbsId = 'someWbsId'; - - const response = await moveTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindSpy).toBeCalled(); - }); - }); - - describe('deleteTask function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `deleteTask` permission is missing', async () => { - const { deleteTask } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to deleteTasks.' }; - - const response = await deleteTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 400 if no Task found', async () => { - const { deleteTask } = makeSut(); - - const error = { error: 'No valid records found' }; - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - mother: 'null', - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValue([]); - const followUpFindOneAndDeleteSpy = jest - .spyOn(FollowUp, 'findOneAndDelete') - .mockResolvedValue(true); - - const response = await deleteTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - expect(followUpFindOneAndDeleteSpy).toHaveBeenCalled(); - }); - - test('Return 200 on successfully deleting task', async () => { - const { deleteTask } = makeSut(); - - const message = { message: 'Task successfully deleted' }; - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - mother: 'null', - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValue([ - { - remove: jest.fn().mockImplementation(() => Promise.resolve(1)), - }, - ]); - const followUpFindOneAndDeleteSpy = jest - .spyOn(FollowUp, 'findOneAndDelete') - .mockResolvedValue(true); - - const response = await deleteTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, message, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - expect(followUpFindOneAndDeleteSpy).toHaveBeenCalled(); - }); - }); - - describe('deleteTaskByWBS function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `deleteTask` permission is missing', async () => { - const { deleteTaskByWBS } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to deleteTasks.' }; - - const response = await deleteTaskByWBS(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 400 if no Task found', async () => { - const { deleteTaskByWBS } = makeSut(); - - const error = { error: 'No valid records found' }; - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - wbsId: 456, - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockImplementation((query, callback) => { - callback(null, []); - return { - catch: jest.fn(), - }; - }); - - const response = await deleteTaskByWBS(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - }); - - test('Return 400 if Task.find fails', async () => { - const { deleteTaskByWBS } = makeSut(); - - const expectedError = new Error('Database error'); - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - wbsId: 456, - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockImplementation((query, callback) => { - callback(expectedError, null); - return { - catch: jest.fn((catchCallback) => { - catchCallback(expectedError); - return Promise.resolve(); - }), - }; - }); - - const response = await deleteTaskByWBS(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, expectedError, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - }); - - test('Return 200 on successfully deleting task', async () => { - const { deleteTaskByWBS } = makeSut(); - - const message = { message: ' Tasks were successfully deleted' }; - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - wbsId: 456, - }; - - const taskFindSpy = jest.spyOn(Task, 'find').mockImplementation((query, callback) => { - callback(null, [ - { - remove: jest.fn().mockImplementation(() => Promise.resolve(1)), - }, - ]); - return { - catch: jest.fn(), - }; - }); - - const followUpFindOneAndDeleteSpy = jest - .spyOn(FollowUp, 'findOneAndDelete') - .mockResolvedValue(true); - - const response = await deleteTaskByWBS(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, message, response, mockRes); - expect(taskFindSpy).toHaveBeenCalled(); - expect(followUpFindOneAndDeleteSpy).toHaveBeenCalled(); - }); - }); - - describe('updateTask function()', () => { - const mockedTask = { - wbs: 111, - }; - const mockedWBS = { - projectId: 111, - modifiedDatetime: new Date(), - save: jest.fn(), - }; - const mockedProject = { - projectId: 111, - modifiedDatetime: new Date(), - save: jest.fn(), - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `updateTask` permission is missing', async () => { - const { updateTask } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to update Task.' }; - - const response = await updateTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 200 on successful update', async () => { - const { updateTask } = makeSut(); - - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValue(mockedTask); - const taskFindOneAndUpdateSpy = jest - .spyOn(Task, 'findOneAndUpdate') - .mockResolvedValueOnce(true); - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockedWBS); - const projectFindByIdSpy = jest.spyOn(Project, 'findById').mockResolvedValue(mockedProject); - - const response = await updateTask(mockReq, mockRes); - await flushPromises(); - - // assertResMock(201, null, response, mockRes); - expect(mockRes.status).toBeCalledWith(201); - expect(response).toBeUndefined(); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindOneAndUpdateSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(projectFindByIdSpy).toHaveBeenCalled(); - }); - - test('Return 404 on encountering error', async () => { - const { updateTask } = makeSut(); - - const error = { error: 'No valid records found' }; - hasPermission.mockResolvedValueOnce(true); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValue(mockedTask); - const taskFindOneAndUpdateSpy = jest - .spyOn(Task, 'findOneAndUpdate') - .mockRejectedValueOnce(error); - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockedWBS); - const projectFindByIdSpy = jest.spyOn(Project, 'findById').mockResolvedValue(mockedProject); - - const response = await updateTask(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindOneAndUpdateSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(projectFindByIdSpy).toHaveBeenCalled(); - }); - }); - - describe('swap function()', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Return 403 if `swapTask` permission is missing', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(false); - - const error = { error: 'You are not authorized to create new projects.' }; - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(403, error, response, mockRes); - }); - - test('Return 400 if `taskId1` is missing', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = null; - mockReq.body.taskId2 = 'some-value'; - - const error = { error: 'taskId1 and taskId2 are mandatory fields' }; - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 400 if `taskId2` is missing', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'some-value'; - mockReq.body.taskId2 = null; - - const error = { error: 'taskId1 and taskId2 are mandatory fields' }; - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 400 if `taskId1` and `taskId2` are missing', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = null; - mockReq.body.taskId2 = null; - - const error = { error: 'taskId1 and taskId2 are mandatory fields' }; - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Return 400 if no task exists with the id same as `taskId1`', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'invalid-taskId1'; - mockReq.body.taskId2 = 'some value'; - - const error = 'No valid records found'; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'invalid-taskId1') { - callback(null, null); // the first null shows no error | second null show no task1 - } else if (id === 'invalid-taskId2') { - callback(null, 'some task2 exists'); - } - }); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - }); - - test('Return 400 if no task exists with the id same as `taskId2`', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'valid-taskId1'; - mockReq.body.taskId2 = 'invalid-taskId2'; - - const error = 'No valid records found'; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'valid-taskId1') { - callback(null, { _id: 'valid-taskId1', name: 'Task 1' }); - } - - if (id === 'invalid-taskId2') { - callback(null, null); // the first null shows no error | second null show no task2 found - } - }); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalledTimes(2); - expect(taskFindByIdSpy).toHaveBeenNthCalledWith(1, 'valid-taskId1', expect.any(Function)); - expect(taskFindByIdSpy).toHaveBeenNthCalledWith(2, 'invalid-taskId2', expect.any(Function)); - }); - - test('Return 400 if some error occurs while saving task1', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'valid-taskId1'; - mockReq.body.taskId2 = 'valid-taskId2'; - - const error = 'some error'; - - const validTask1 = { - _id: 'valid-taskId1', - name: 'Task 1', - num: 1, - parentId: 'pId', - save: jest.fn().mockRejectedValue(error), - }; - - const validTask2 = { - _id: 'valid-taskId2', - name: 'Task 2', - num: 2, - parentId: 'pId', - save: jest.fn().mockResolvedValue('sadasd'), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'valid-taskId1') { - callback(null, validTask1); - } - if (id === 'valid-taskId2') { - callback(null, validTask2); - } - }); - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce('works fine'); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalled(); - }); - - test('Return 400 if some error occurs while saving task2', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'valid-taskId1'; - mockReq.body.taskId2 = 'valid-taskId2'; - - const error = 'some error'; - - const validTask1 = { - _id: 'valid-taskId1', - name: 'Task 1', - num: 1, - parentId: 'pId', - save: jest.fn().mockResolvedValue(), - }; - - const validTask2 = { - _id: 'valid-taskId2', - name: 'Task 2', - num: 2, - parentId: 'pId', - save: jest.fn().mockRejectedValue(error), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'valid-taskId1') { - callback(null, validTask1); - } - if (id === 'valid-taskId2') { - callback(null, validTask2); - } - }); - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce('works fine'); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalled(); - }); - - test('Return 404 if some error occurs while saving task.find', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'valid-taskId1'; - mockReq.body.taskId2 = 'valid-taskId2'; - - const error = 'some error'; - - const validTask1 = { - _id: 'valid-taskId1', - name: 'Task 1', - num: 1, - parentId: 'pId', - save: jest.fn().mockResolvedValue(), - }; - - const validTask2 = { - _id: 'valid-taskId2', - name: 'Task 2', - num: 2, - parentId: 'pId', - save: jest.fn().mockResolvedValue(), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'valid-taskId1') { - callback(null, validTask1); - } - if (id === 'valid-taskId2') { - callback(null, validTask2); - } - }); - - const taskFindSpy = jest.spyOn(Task, 'find').mockRejectedValueOnce(error); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalled(); - }); - - test('Return 200 if swapped correctly', async () => { - const { swap } = makeSut(); - hasPermission.mockResolvedValueOnce(true); - - mockReq.body.taskId1 = 'valid-taskId1'; - mockReq.body.taskId2 = 'valid-taskId2'; - - const message = 'no error'; - - const validTask1 = { - _id: 'valid-taskId1', - name: 'Task 1', - num: 1, - parentId: 'pId', - save: jest.fn().mockResolvedValue(), - }; - - const validTask2 = { - _id: 'valid-taskId2', - name: 'Task 2', - num: 2, - parentId: 'pId', - save: jest.fn().mockResolvedValue(), - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockImplementation((id, callback) => { - if (id === 'valid-taskId1') { - callback(null, validTask1); - } - if (id === 'valid-taskId2') { - callback(null, validTask2); - } - }); - - const taskFindSpy = jest.spyOn(Task, 'find').mockResolvedValueOnce(message); - - const response = await swap(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, message, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindSpy).toHaveBeenCalled(); - }); - }); - - describe('getTaskById function()', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test('Returns 400 if the taskId is missing from the params', async () => { - const { getTaskById } = makeSut(); - - mockReq.params.id = null; - - const error = { error: 'Task ID is missing' }; - - const response = await getTaskById(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - }); - - test('Returns 400 if the taskId is missing from the params', async () => { - const { getTaskById } = makeSut(); - - mockReq.params.id = 'someTaskId'; - - const error = { error: 'This is not a valid task' }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValueOnce(null); - - const response = await getTaskById(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - }); - - test('Returns 500 if some error occurs at Task.findById', async () => { - const { getTaskById } = makeSut(); - - mockReq.params.id = 'someTaskId'; - - const error = new Error('some error occurred'); - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockRejectedValueOnce(error); - - const response = await getTaskById(mockReq, mockRes); - await flushPromises(); - - assertResMock( - 500, - { error: 'Internal Server Error', details: error.message }, - response, - mockRes, - ); - expect(taskFindByIdSpy).toHaveBeenCalled(); - }); - - test('Returns 200 if some error occurs at Task.findById', async () => { - const { getTaskById } = makeSut(); - - mockReq.params.id = 'someTaskId'; - - const mockTask = { - resources: [], - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValueOnce(mockTask); - - const response = await getTaskById(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, mockTask, response, mockRes); - expect(taskFindByIdSpy).toHaveBeenCalled(); - }); - }); - - describe('fixTasks function()', () => { - test('Returns 200 without performing any action', async () => { - const { fixTasks } = makeSut(); - - const response = fixTasks(mockReq, mockRes); - - await flushPromises(); - - assertResMock(200, 'done', response, mockRes); - }); - }); - - describe('updateAllParents function()', () => { - test('Returns 200 Task.Find() on successful operation', async () => { - const { updateAllParents } = makeSut(); - - const mockTasks = []; - - const taskFind = jest.spyOn(Task, 'find').mockResolvedValueOnce(mockTasks); - const response = updateAllParents(mockReq, mockRes); - - await flushPromises(); - - assertResMock(200, 'done', response, mockRes); - expect(taskFind).toHaveBeenCalled(); - }); - - test('Returns 400 on some error', async () => { - const { updateAllParents } = makeSut(); - - const error = new Error('some error'); - - const taskFind = jest.spyOn(Task, 'find').mockImplementationOnce(() => { - throw error; - }); - const response = await updateAllParents(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, error, response, mockRes); - expect(taskFind).toHaveBeenCalled(); - }); - }); - - describe('getTasksByUserId function()', () => { - test('Returns 200 and tasks when aggregation is successful', async () => { - const { getTasksByUserId } = makeSut(); - - mockReq.params.userId = '507f1f77bcf86cd799439011'; - - const mockTasks = [ - { _id: 'task1', taskName: 'Task 1', wbsName: 'WBS 1', projectName: 'Project 1' }, - { _id: 'task2', taskName: 'Task 2', wbsName: 'WBS 2', projectName: 'Project 2' }, - ]; - - // Mock the Task.aggregate method - const mockAggregate = { - match: jest.fn().mockReturnThis(), - lookup: jest.fn().mockReturnThis(), - unwind: jest.fn().mockReturnThis(), - addFields: jest.fn().mockReturnThis(), - project: jest.fn().mockReturnThis(), - }; - - mockAggregate.project.mockResolvedValue(mockTasks); - - const taskAggregate = jest.spyOn(Task, 'aggregate').mockReturnValue(mockAggregate); - - const response = await getTasksByUserId(mockReq, mockRes); - - assertResMock(200, mockTasks, response, mockRes); - expect(taskAggregate).toHaveBeenCalled(); - }); - - test('Returns 400 when error occurs', async () => { - const { getTasksByUserId } = makeSut(); - - mockReq.params.userId = '507f1f77bcf86cd799439011'; - - const mockError = new Error('some error'); - - // Mock the Task.aggregate method - const mockAggregate = { - match: jest.fn().mockReturnThis(), - lookup: jest.fn().mockReturnThis(), - unwind: jest.fn().mockReturnThis(), - addFields: jest.fn().mockReturnThis(), - project: jest.fn().mockReturnThis(), - }; - - mockAggregate.project.mockRejectedValueOnce(mockError); - - const taskAggregate = jest.spyOn(Task, 'aggregate').mockReturnValue(mockAggregate); - - const response = await getTasksByUserId(mockReq, mockRes); - - assertResMock(400, mockError, response, mockRes); - expect(taskAggregate).toHaveBeenCalled(); - }); - }); - - describe('sendReviewReq function()', () => { - test('Returns 200 on success', async () => { - const { sendReviewReq } = makeSut(); - - mockReq.body = { - ...mockReq.body, - myUserId: 'id', - name: 'name', - taskName: 'task', - }; - - const userProfileFindByIdSpy = jest.spyOn(UserProfile, 'findById').mockResolvedValueOnce([]); - const userProfileFindSpy = jest.spyOn(UserProfile, 'find').mockResolvedValueOnce([]); - - const response = await sendReviewReq(mockReq, mockRes); - - assertResMock(200, 'Success', response, mockRes); - expect(emailSender).toHaveBeenCalledWith( - [], - expect.any(String), - expect.any(String), - null, - null, - ); - expect(userProfileFindByIdSpy).toHaveBeenCalled(); - expect(userProfileFindSpy).toHaveBeenCalled(); - }); - - test('Returns 400 on error', async () => { - const { sendReviewReq } = makeSut(); - - mockReq.body = { - ...mockReq.body, - myUserId: 'id', - name: 'name', - taskName: 'task', - }; - - const mockError = new Error('some error'); - - emailSender.mockImplementation(() => mockError); - - const userProfileFindByIdSpy = jest.spyOn(UserProfile, 'findById').mockResolvedValueOnce([]); - const userProfileFindSpy = jest.spyOn(UserProfile, 'find').mockResolvedValueOnce([]); - - const response = await sendReviewReq(mockReq, mockRes); - - assertResMock(400, mockError, response, mockRes); - - expect(emailSender).toHaveBeenCalledWith( - [], - expect.any(String), - expect.any(String), - null, - null, - ); - expect(userProfileFindByIdSpy).toHaveBeenCalled(); - expect(userProfileFindSpy).toHaveBeenCalled(); - }); - }); - - describe('getTasksForTeamsByUser function()', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('Returns 200 on success - getTasksForTeams', async () => { - mockReq.params.userId = 1234; - const mockData = ['mockData']; - - taskHelperMethods.getTasksForTeams.mockResolvedValueOnce(mockData); - - const { getTasksForTeamsByUser } = makeSut(); - - const response = await getTasksForTeamsByUser(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, mockData, response, mockRes); - }); - - test('Returns 200 on success - getTasksForTeamsByUser', async () => { - mockReq.params.userId = 1234; - const mockData = ['mockData']; - - const execMock = { - exec: jest.fn().mockResolvedValueOnce(mockData), - }; - - taskHelperMethods.getTasksForTeams.mockResolvedValueOnce([]); - taskHelperMethods.getTasksForSingleUser.mockImplementation(() => execMock); - - const { getTasksForTeamsByUser } = makeSut(); - - const response = await getTasksForTeamsByUser(mockReq, mockRes); - await flushPromises(); - - assertResMock(200, mockData, response, mockRes); - }); - - test('Returns 400 on error', async () => { - mockReq.params.userId = 1234; - const mockError = new Error('error'); - - taskHelperMethods.getTasksForTeams.mockRejectedValueOnce(mockError); - - const { getTasksForTeamsByUser } = makeSut(); - - const response = await getTasksForTeamsByUser(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, { error: mockError }, response, mockRes); - }); - }); - - describe('updateTaskStatus function()', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const mockedTask = { - wbs: 111, - }; - const mockedWBS = { - projectId: 111, - modifiedDatetime: new Date(), - save: jest.fn(), - }; - const mockedProject = { - projectId: 111, - modifiedDatetime: new Date(), - save: jest.fn(), - }; - - test('Returns 200 on success - updateTaskStatus', async () => { - const { updateTaskStatus } = makeSut(); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValue(mockedTask); - const taskFindOneAndUpdateSpy = jest - .spyOn(Task, 'findOneAndUpdate') - .mockResolvedValueOnce(true); - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockedWBS); - const projectFindByIdSpy = jest.spyOn(Project, 'findById').mockResolvedValue(mockedProject); - - const response = await updateTaskStatus(mockReq, mockRes); - await flushPromises(); - - // assertResMock(201, null, response, mockRes); - expect(mockRes.status).toBeCalledWith(201); - expect(response).toBeUndefined(); - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindOneAndUpdateSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(projectFindByIdSpy).toHaveBeenCalled(); - }); - - test('Returns 400 on error', async () => { - const { updateTaskStatus } = makeSut(); - const error = new Error('some error'); - - mockReq.params = { - ...mockReq.params, - taskId: 456, - }; - - const taskFindByIdSpy = jest.spyOn(Task, 'findById').mockResolvedValue(mockedTask); - const taskFindOneAndUpdateSpy = jest - .spyOn(Task, 'findOneAndUpdate') - .mockRejectedValueOnce(error); - const wbsFindByIdSpy = jest.spyOn(WBS, 'findById').mockResolvedValue(mockedWBS); - const projectFindByIdSpy = jest.spyOn(Project, 'findById').mockResolvedValue(mockedProject); - - const response = await updateTaskStatus(mockReq, mockRes); - await flushPromises(); - - assertResMock(404, error, response, mockRes); - - expect(taskFindByIdSpy).toHaveBeenCalled(); - expect(taskFindOneAndUpdateSpy).toHaveBeenCalled(); - expect(wbsFindByIdSpy).toHaveBeenCalled(); - expect(projectFindByIdSpy).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js index 42d9d8d25..41f515e99 100644 --- a/src/controllers/teamController.js +++ b/src/controllers/teamController.js @@ -6,66 +6,8 @@ const Logger = require('../startup/logger'); const teamcontroller = function (Team) { const getAllTeams = function (req, res) { - Team.aggregate([ - { - $unwind: '$members', - }, - { - $lookup: { - from: 'userProfiles', - localField: 'members.userId', - foreignField: '_id', - as: 'userProfile', - }, - }, - { - $unwind: '$userProfile', - }, - { - $match: { - isActive: true, - } - }, - { - $group: { - _id: { - teamId: '$_id', - teamCode: '$userProfile.teamCode', - }, - count: { $sum: 1 }, - teamName: { $first: '$teamName' }, - members: { - $push: { - _id: '$userProfile._id', - name: '$userProfile.name', - email: '$userProfile.email', - teamCode: '$userProfile.teamCode', - addDateTime: '$members.addDateTime', - }, - }, - createdDatetime: { $first: '$createdDatetime' }, - modifiedDatetime: { $first: '$modifiedDatetime' }, - isActive: { $first: '$isActive' }, - }, - }, - { - $sort: { count: -1 }, // Sort by the most frequent teamCode - }, - { - $group: { - _id: '$_id.teamId', - teamCode: { $first: '$_id.teamCode' }, // Get the most frequent teamCode - teamName: { $first: '$teamName' }, - members: { $first: '$members' }, - createdDatetime: { $first: '$createdDatetime' }, - modifiedDatetime: { $first: '$modifiedDatetime' }, - isActive: { $first: '$isActive' }, - }, - }, - { - $sort: { teamName: 1 }, // Sort teams by name - }, - ]) + Team.find({}) + .sort({ teamName: 1 }) .then((results) => res.status(200).send(results)) .catch((error) => { Logger.logException(error); @@ -168,15 +110,14 @@ const teamcontroller = function (Team) { return; } - // Removed the permission check as the permission check if done in earlier - // const canEditTeamCode = - // req.body.requestor.role === 'Owner' || - // req.body.requestor.permissions?.frontPermissions.includes('editTeamCode'); + const canEditTeamCode = + req.body.requestor.role === 'Owner' || + req.body.requestor.permissions?.frontPermissions.includes('editTeamCode'); - // if (!canEditTeamCode) { - // res.status(403).send('You are not authorized to edit team code.'); - // return; - // } + if (!canEditTeamCode) { + res.status(403).send('You are not authorized to edit team code.'); + return; + } record.teamName = req.body.teamName; record.isActive = req.body.isActive; @@ -281,62 +222,59 @@ const teamcontroller = function (Team) { }, }, ]) - .then((result) => { - res.status(200).send(result) - }) + .then((result) => res.status(200).send(result)) .catch((error) => { Logger.logException(error, null, `TeamId: ${teamId} Request:${req.body}`); - return res.status(500).send(error); + res.status(500).send(error); }); }; const updateTeamVisibility = async (req, res) => { - console.log('==============> 9 '); + console.log("==============> 9 "); const { visibility, teamId, userId } = req.body; - + try { Team.findById(teamId, (error, team) => { if (error || team === null) { res.status(400).send('No valid records found'); return; } - - const memberIndex = team.members.findIndex((member) => member.userId.toString() === userId); + + const memberIndex = team.members.findIndex(member => member.userId.toString() === userId); if (memberIndex === -1) { res.status(400).send('Member not found in the team.'); return; } - + team.members[memberIndex].visible = visibility; team.modifiedDatetime = Date.now(); - - team - .save() - .then((updatedTeam) => { - // Additional operations after team.save() + + team.save() + .then(updatedTeam => { + // Additional operations after team.save() const assignlist = []; const unassignlist = []; - team.members.forEach((member) => { + team.members.forEach(member => { if (member.userId.toString() === userId) { // Current user, no need to process further return; } - + if (visibility) { assignlist.push(member.userId); } else { - console.log('Visiblity set to false so removing it'); + console.log("Visiblity set to false so removing it"); unassignlist.push(member.userId); } }); - + const addTeamToUserProfile = userProfile .updateMany({ _id: { $in: assignlist } }, { $addToSet: { teams: teamId } }) .exec(); const removeTeamFromUserProfile = userProfile .updateMany({ _id: { $in: unassignlist } }, { $pull: { teams: teamId } }) .exec(); - + Promise.all([addTeamToUserProfile, removeTeamFromUserProfile]) .then(() => { res.status(200).send({ result: 'Done' }); @@ -345,17 +283,18 @@ const teamcontroller = function (Team) { res.status(500).send({ error }); }); }) - .catch((errors) => { + .catch(errors => { console.error('Error saving team:', errors); res.status(400).send(errors); }); + }); } catch (error) { - res.status(500).send(`Error updating team visibility: ${error.message}`); + res.status(500).send(`Error updating team visibility: ${ error.message}`); } }; - /** + /** * Leaner version of the teamcontroller.getAllTeams * Remove redundant data: members, isActive, createdDatetime, modifiedDatetime. */ @@ -369,48 +308,7 @@ const teamcontroller = function (Team) { res.status(500).send('Fetch team code failed.'); }); }; - - const getAllTeamMembers = async function (req,res) { - try{ - const teamIds = req.body; - const cacheKey='teamMembersCache' - if(cache.hasCache(cacheKey)){ - let data=cache.getCache('teamMembersCache') - return res.status(200).send(data); - } - if (!Array.isArray(teamIds) || teamIds.length === 0 || !teamIds.every(team => mongoose.Types.ObjectId.isValid(team._id))) { - return res.status(400).send({ error: 'Invalid request: teamIds must be a non-empty array of valid ObjectId strings.' }); - } - let data = await Team.aggregate([ - { - $match: { _id: { $in: teamIds.map(team => mongoose.Types.ObjectId(team._id)) } } - }, - { $unwind: '$members' }, - { - $lookup: { - from: 'userProfiles', - localField: 'members.userId', - foreignField: '_id', - as: 'userProfile', - }, - }, - { $unwind: { path: '$userProfile', preserveNullAndEmptyArrays: true } }, - { - $group: { - _id: '$_id', // Group by team ID - teamName: { $first: '$teamName' }, // Use $first to keep the team name - createdDatetime: { $first: '$createdDatetime' }, - members: { $push: '$members' }, // Rebuild the members array - }, - }, - ]) - cache.setCache(cacheKey,data) - res.status(200).send(data); - }catch(error){ - console.log(error) - res.status(500).send({'message':"Fetching team members failed"}); - } - } + return { getAllTeams, getAllTeamCode, @@ -421,7 +319,6 @@ const teamcontroller = function (Team) { assignTeamToUsers, getTeamMembership, updateTeamVisibility, - getAllTeamMembers }; }; diff --git a/src/controllers/timeEntryController.js b/src/controllers/timeEntryController.js index 7f17efd31..6e8d36596 100644 --- a/src/controllers/timeEntryController.js +++ b/src/controllers/timeEntryController.js @@ -1,6 +1,5 @@ const moment = require('moment-timezone'); const mongoose = require('mongoose'); -const { v4: uuidv4 } = require('uuid'); const logger = require('../startup/logger'); const UserProfile = require('../models/userProfile'); const Project = require('../models/project'); @@ -594,7 +593,7 @@ const timeEntrycontroller = function (TimeEntry) { await timeEntry.save({ session }); if (userprofile) { - await userprofile.save({ session, validateModifiedOnly: true }); + await userprofile.save({ session }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); } @@ -867,7 +866,7 @@ const timeEntrycontroller = function (TimeEntry) { } await timeEntry.save({ session }); if (userprofile) { - await userprofile.save({ session, validateModifiedOnly: true }); + await userprofile.save({ session }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); @@ -940,7 +939,7 @@ const timeEntrycontroller = function (TimeEntry) { await timeEntry.remove({ session }); if (userprofile) { - await userprofile.save({ session, validateModifiedOnly: true }); + await userprofile.save({ session }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); @@ -1063,115 +1062,40 @@ const timeEntrycontroller = function (TimeEntry) { }); }; - const getTimeEntriesForReports =async function (req, res) { + const getTimeEntriesForReports = function (req, res) { const { users, fromDate, toDate } = req.body; - const cacheKey = `timeEntry_${fromDate}_${toDate}`; - const timeentryCache=cacheClosure(); - const cacheData=timeentryCache.hasCache(cacheKey) - if(cacheData){ - let data=timeentryCache.getCache(cacheKey); - return res.status(200).send(data); - } - try { - const results = await TimeEntry.find( - { - personId: { $in: users }, - dateOfWork: { $gte: fromDate, $lte: toDate }, - }, - '-createdDateTime' // Exclude unnecessary fields - ) - .lean() // Returns plain JavaScript objects, not Mongoose documents - .populate({ - path: 'projectId', - select: '_id projectName', // Only return necessary fields from the project - }) - .exec(); // Executes the query - const data = results.map(element => { - const record = { - _id: element._id, - isTangible: element.isTangible, - personId: element.personId, - dateOfWork: element.dateOfWork, - hours: formatSeconds(element.totalSeconds)[0], - minutes: formatSeconds(element.totalSeconds)[1], - projectId: element.projectId?._id || '', - projectName: element.projectId?.projectName || '', - }; - return record; - }); - timeentryCache.setCache(cacheKey,data); - return res.status(200).send(data); - } catch (error) { - res.status(400).send(error); - } - }; - const getTimeEntriesForProjectReports = function (req, res) { - const { users, fromDate, toDate } = req.body; - - // Fetch only necessary fields and avoid bringing the entire document TimeEntry.find( { personId: { $in: users }, dateOfWork: { $gte: fromDate, $lte: toDate }, }, - 'totalSeconds isTangible dateOfWork projectId', + ' -createdDateTime', ) - .populate('projectId', 'projectName _id') - .lean() // lean() for better performance as we don't need Mongoose document methods + .populate('projectId') + .then((results) => { - const data = results.map((element) => { - const record = { - isTangible: element.isTangible, - dateOfWork: element.dateOfWork, - projectId: element.projectId ? element.projectId._id : '', - projectName: element.projectId ? element.projectId.projectName : '', - }; - - // Convert totalSeconds to hours and minutes - [record.hours, record.minutes] = formatSeconds(element.totalSeconds); + const data = []; - return record; + results.forEach((element) => { + const record = {}; + record._id = element._id; + record.isTangible = element.isTangible; + record.personId = element.personId._id; + record.dateOfWork = element.dateOfWork; + [record.hours, record.minutes] = formatSeconds(element.totalSeconds); + record.projectId = element.projectId ? element.projectId._id : ''; + record.projectName = element.projectId ? element.projectId.projectName : ''; + data.push(record); }); res.status(200).send(data); }) .catch((error) => { - res.status(400).send({ message: 'Error fetching time entries for project reports', error }); + res.status(400).send(error); }); }; - const getTimeEntriesForPeopleReports = async function (req, res) { - try { - const { users, fromDate, toDate } = req.body; - - const results = await TimeEntry.find( - { - personId: { $in: users }, - dateOfWork: { $gte: fromDate, $lte: toDate }, - }, - 'personId totalSeconds isTangible dateOfWork', - ).lean(); // Use lean() for better performance - - const data = results - .map((entry) => { - const [hours, minutes] = formatSeconds(entry.totalSeconds); - return { - personId: entry.personId, - hours, - minutes, - isTangible: entry.isTangible, - dateOfWork: entry.dateOfWork, - }; - }) - .filter(Boolean); - - res.status(200).send(data); - } catch (error) { - res.status(400).send({ message: 'Error fetching time entries for people reports', error }); - } - }; - /** * Get time entries for a specified project */ @@ -1284,12 +1208,7 @@ const timeEntrycontroller = function (TimeEntry) { */ const getLostTimeEntriesForTeamList = function (req, res) { const { teams, fromDate, toDate } = req.body; - const lostteamentryCache=cacheClosure() - const cacheKey='LostTeamEntry'+`_${fromDate}`+`_${toDate}`; - const cacheData=lostteamentryCache.getCache(cacheKey) - if(cacheData){ - return res.status(200).send(cacheData) - } + TimeEntry.find( { entryType: 'team', @@ -1298,7 +1217,7 @@ const timeEntrycontroller = function (TimeEntry) { isActive: { $ne: false }, }, ' -createdDateTime', - ).lean() + ) .populate('teamId') .sort({ lastModifiedDateTime: -1 }) .then((results) => { @@ -1315,8 +1234,7 @@ const timeEntrycontroller = function (TimeEntry) { [record.hours, record.minutes] = formatSeconds(element.totalSeconds); data.push(record); }); - lostteamentryCache.setCache(cacheKey,data); - return res.status(200).send(data); + res.status(200).send(data); }) .catch((error) => { res.status(400).send(error); @@ -1449,12 +1367,10 @@ const timeEntrycontroller = function (TimeEntry) { return newTotalIntangibleHrs; }; - const recalculationTaskQueue = []; - /** - * recalculate the hoursByCategory for all users and update the field + * recalculate the hoursByCatefory for all users and update the field */ - const recalculateHoursByCategoryAllUsers = async function (taskId) { + const recalculateHoursByCategoryAllUsers = async function (req, res) { const session = await mongoose.startSession(); session.startTransaction(); @@ -1469,60 +1385,18 @@ const timeEntrycontroller = function (TimeEntry) { await Promise.all(recalculationPromises); await session.commitTransaction(); - - const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); - if (recalculationTask) { - recalculationTask.status = 'Completed'; - recalculationTask.completionTime = new Date().toISOString(); - } + return res.status(200).send({ + message: 'finished the recalculation for hoursByCategory for all users', + }); } catch (err) { await session.abortTransaction(); - const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); - if (recalculationTask) { - recalculationTask.status = 'Failed'; - recalculationTask.completionTime = new Date().toISOString(); - } - logger.logException(err); + return res.status(500).send({ error: err.toString() }); } finally { session.endSession(); } }; - const startRecalculation = async function (req, res) { - const taskId = uuidv4(); - recalculationTaskQueue.push({ - taskId, - status: 'In progress', - startTime: new Date().toISOString(), - completionTime: null, - }); - if (recalculationTaskQueue.length > 10) { - recalculationTaskQueue.shift(); - } - - res.status(200).send({ - message: 'The recalculation task started in the background', - taskId, - }); - - setTimeout(() => recalculateHoursByCategoryAllUsers(taskId), 0); - }; - - const checkRecalculationStatus = async function (req, res) { - const { taskId } = req.params; - const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); - if (recalculationTask) { - res.status(200).send({ - status: recalculationTask.status, - startTime: recalculationTask.startTime, - completionTime: recalculationTask.completionTime, - }); - } else { - res.status(404).send({ message: 'Task not found' }); - } - }; - /** * recalculate the totalIntangibleHrs for all users and update the field */ @@ -1567,12 +1441,9 @@ const timeEntrycontroller = function (TimeEntry) { getLostTimeEntriesForTeamList, backupHoursByCategoryAllUsers, backupIntangibleHrsAllUsers, + recalculateHoursByCategoryAllUsers, recalculateIntangibleHrsAllUsers, getTimeEntriesForReports, - getTimeEntriesForProjectReports, - getTimeEntriesForPeopleReports, - startRecalculation, - checkRecalculationStatus, }; }; diff --git a/src/controllers/timeZoneAPIController.js b/src/controllers/timeZoneAPIController.js index 2023f312a..07c9c0b17 100644 --- a/src/controllers/timeZoneAPIController.js +++ b/src/controllers/timeZoneAPIController.js @@ -1,11 +1,11 @@ // eslint-disable-next-line import/no-extraneous-dependencies const fetch = require('node-fetch'); -const dotenv = require('dotenv'); - -dotenv.config(); const ProfileInitialSetupToken = require('../models/profileInitialSetupToken'); const { hasPermission } = require('../utilities/permissions'); +const premiumKey = process.env.TIMEZONE_PREMIUM_KEY; +const commonKey = process.env.TIMEZONE_COMMON_KEY; + const performTimeZoneRequest = async (req, res, apiKey) => { const { location } = req.params; @@ -17,6 +17,7 @@ const performTimeZoneRequest = async (req, res, apiKey) => { try { const geocodeAPIEndpoint = 'https://api.opencagedata.com/geocode/v1/json'; const url = `${geocodeAPIEndpoint}?key=${apiKey}&q=${location}&pretty=1&limit=1`; + const response = await fetch(url); const data = await response.json(); @@ -52,17 +53,16 @@ const performTimeZoneRequest = async (req, res, apiKey) => { const timeZoneAPIController = function () { const getTimeZone = async (req, res) => { - const premiumKey = process.env.TIMEZONE_PREMIUM_KEY; - const commonKey = process.env.TIMEZONE_COMMON_KEY; const { requestor } = req.body; + if (!requestor.role) { res.status(403).send('Unauthorized Request'); return; } + const userAPIKey = (await hasPermission(requestor, 'getTimeZoneAPIKey')) ? premiumKey : commonKey; - if (!userAPIKey) { res.status(401).send('API Key Missing'); return; @@ -72,7 +72,6 @@ const timeZoneAPIController = function () { }; const getTimeZoneProfileInitialSetup = async (req, res) => { - const commonKey = process.env.TIMEZONE_COMMON_KEY; const { token } = req.body; if (!token) { res.status(400).send('Missing token'); diff --git a/src/controllers/timeZoneAPIController.spec.js b/src/controllers/timeZoneAPIController.spec.js deleted file mode 100644 index 2f3607cc6..000000000 --- a/src/controllers/timeZoneAPIController.spec.js +++ /dev/null @@ -1,316 +0,0 @@ -jest.mock('../utilities/permissions', () => ({ - hasPermission: jest.fn(), // Mocking the hasPermission function -})); - -jest.mock('node-fetch'); -// eslint-disable-next-line import/no-extraneous-dependencies -const fetch = require('node-fetch'); - -const originalPremiumKey = process.env.TIMEZONE_PREMIUM_KEY; -process.env.TIMEZONE_PREMIUM_KEY = 'mockPremiumKey'; - -const originalCommonKey = process.env.TIMEZONE_COMMON_KEY; -delete process.env.TIMEZONE_COMMON_KEY; - -const successfulFetchRequestWithResults = jest.fn(() => - Promise.resolve({ - json: () => - Promise.resolve({ - status: { - code: 200, - message: 'Request Processed Successfully', - }, - results: [ - { - annotations: { - timezone: { - name: 'timeZone - Fiji', - }, - }, - geometry: { - lat: 1, - lng: 1, - }, - components: { - country: 'U.S.', - city: 'Paris', - }, - }, - ], - }), - }), -); - -const successfulFetchRequestWithNoResults = jest.fn(() => - Promise.resolve({ - json: () => - Promise.resolve({ - status: { - code: 200, - message: 'Request Processed Successfully', - }, - results: [], - }), - }), -); - -const unsuccessfulFetchRequestInternalServerError = jest.fn(() => - Promise.resolve({ - json: () => - Promise.resolve({ - status: { - code: null, - message: 'Internal Server Error', - }, - results: [], - }), - }), -); - -const { hasPermission } = require('../utilities/permissions'); -const timeZoneAPIController = require('./timeZoneAPIController'); -const ProfileInitialSetupToken = require('../models/profileInitialSetupToken'); -const { mockReq, mockRes, assertResMock } = require('../test'); - -const flushPromises = () => new Promise(setImmediate); -const makeSut = () => { - const { getTimeZone, getTimeZoneProfileInitialSetup } = timeZoneAPIController(); - return { getTimeZone, getTimeZoneProfileInitialSetup }; -}; - -describe('timeZoneAPIController Unit Tests', () => { - afterAll(() => { - // Reseting TIMEZONE_PREMIUM_KEY and TIMEZONE_COMMON_KEY environment variables to their original values - if (originalPremiumKey) { - process.env.TIMEZONE_PREMIUM_KEY = originalPremiumKey; - } else { - delete process.env.TIMEZONE_PREMIUM_KEY; - } - - if (originalCommonKey) { - process.env.TIMEZONE_COMMON_KEY = originalCommonKey; - } else { - delete process.env.TIMEZONE_COMMON_KEY; - } - }); - - describe('getTimeZone() function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - beforeEach(() => { - hasPermission.mockResolvedValue(true); - }); - test('Returns 403, as requestor.role is missing in request body', async () => { - const { getTimeZone } = makeSut(); - - // setting request.role to `Null` - mockReq.body.requestor.role = null; - - const response = await getTimeZone(mockReq, mockRes); - - assertResMock(403, 'Unauthorized Request', response, mockRes); - }); - - test('Returns 401, as API is missing', async () => { - delete process.env.TIMEZONE_COMMON_KEY; - - const { getTimeZone } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - - hasPermission.mockResolvedValue(false); - - const response = await getTimeZone(mockReq, mockRes); - await flushPromises(); - - expect(hasPermission).toBeCalledTimes(1); - assertResMock(401, 'API Key Missing', response, mockRes); - }); - - test('Returns 400, when `location` is missing in req.params', async () => { - const { getTimeZone } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - - const response = await getTimeZone(mockReq, mockRes); - await flushPromises(); - - expect(hasPermission).toBeCalledTimes(1); - assertResMock(400, 'Missing location', response, mockRes); - }); - - test('Returns 500, when status.code !== 200 and status code is missing', async () => { - const { getTimeZone } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - fetch.mockImplementation(unsuccessfulFetchRequestInternalServerError); - - const response = await getTimeZone(mockReq, mockRes); - await flushPromises(); - - expect(fetch).toHaveBeenCalledTimes(1); - expect(hasPermission).toBeCalledTimes(1); - assertResMock(500, 'opencage error- Internal Server Error', response, mockRes); - }); - - test('Returns 404, when status.code == 200 and data.results is empty', async () => { - const { getTimeZone } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - fetch.mockImplementation(successfulFetchRequestWithNoResults); - - const response = await getTimeZone(mockReq, mockRes); - await flushPromises(); - - expect(fetch).toHaveBeenCalledTimes(1); - expect(hasPermission).toBeCalledTimes(1); - assertResMock(404, 'No results found', response, mockRes); - }); - - test('Returns 200, when status.code == 200 and data.results is not empty', async () => { - const { getTimeZone } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - fetch.mockImplementation(successfulFetchRequestWithResults); - const timezone = 'timeZone - Fiji'; // mocking the timezone data to be returned by `successfulFetchRequestWithResults` - const currentLocation = { - // mocking the currentLocation data to be returned by `successfulFetchRequestWithResults` - userProvided: mockReq.params.location, - coords: { - lat: 1, - lng: 1, - }, - country: 'U.S.', - city: 'Paris', - }; - - const response = await getTimeZone(mockReq, mockRes); - await flushPromises(); - - expect(fetch).toHaveBeenCalledTimes(1); - expect(hasPermission).toBeCalledTimes(1); - assertResMock(200, { timezone, currentLocation }, response, mockRes); - }); - }); - - describe('getTimeZoneProfileInitialSetup() function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - beforeEach(() => { - hasPermission.mockResolvedValue(true); - }); - - test('Returns status code 400 if token is missing in request.body', async () => { - mockReq.body.token = null; - - const { getTimeZoneProfileInitialSetup } = makeSut(); - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - assertResMock(400, 'Missing token', response, mockRes); - }); - - test('Returns status code 403 if token is missing in request.body', async () => { - mockReq.body.token = 'random_token_value'; - - const { getTimeZoneProfileInitialSetup } = makeSut(); - const profileInitialSetupTokenFindOneSpy = jest - .spyOn(ProfileInitialSetupToken, 'findOne') - .mockReturnValue(null); - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - expect(profileInitialSetupTokenFindOneSpy).toBeCalledTimes(1); - assertResMock(403, 'Unauthorized Request', response, mockRes); - }); - - test('Returns 500, when status.code !== 200 and status code is missing', async () => { - const { getTimeZoneProfileInitialSetup } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - const profileInitialSetupTokenFindOneSpy = jest - .spyOn(ProfileInitialSetupToken, 'findOne') - .mockReturnValue('token'); - fetch.mockImplementation(unsuccessfulFetchRequestInternalServerError); - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - expect(profileInitialSetupTokenFindOneSpy).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledTimes(1); - assertResMock(500, 'opencage error- Internal Server Error', response, mockRes); - }); - - test('Returns 404, when status.code == 200 and data.results is empty', async () => { - const { getTimeZoneProfileInitialSetup } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - const profileInitialSetupTokenFindOneSpy = jest - .spyOn(ProfileInitialSetupToken, 'findOne') - .mockReturnValue('token'); - fetch.mockImplementation(successfulFetchRequestWithNoResults); - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - expect(profileInitialSetupTokenFindOneSpy).toBeCalledTimes(1); - expect(fetch).toHaveBeenCalledTimes(1); - assertResMock(404, 'No results found', response, mockRes); - }); - - test('Returns 200, when status.code == 200 and data.results is not empty', async () => { - const { getTimeZoneProfileInitialSetup } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = 'New Jersey'; - - const profileInitialSetupTokenFindOneSpy = jest - .spyOn(ProfileInitialSetupToken, 'findOne') - .mockReturnValue('token'); - fetch.mockImplementation(successfulFetchRequestWithResults); - - const timezone = 'timeZone - Fiji'; // mocking the timezone data to be returned by `successfulFetchRequestWithResults` - const currentLocation = { - // mocking the currentLocation data to be returned by `successfulFetchRequestWithResults` - userProvided: mockReq.params.location, - coords: { - lat: 1, - lng: 1, - }, - country: 'U.S.', - city: 'Paris', - }; - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - expect(profileInitialSetupTokenFindOneSpy).toBeCalledTimes(1); - expect(fetch).toHaveBeenCalledTimes(1); - assertResMock(200, { timezone, currentLocation }, response, mockRes); - }); - - test('Returns 400, when `location` is missing in req.params', async () => { - const { getTimeZoneProfileInitialSetup } = makeSut(); - mockReq.body.requestor.role = 'Volunteer'; - mockReq.params.location = null; - - const profileInitialSetupTokenFindOneSpy = jest - .spyOn(ProfileInitialSetupToken, 'findOne') - .mockReturnValue('token'); - - const response = await getTimeZoneProfileInitialSetup(mockReq, mockRes); - await flushPromises(); - - expect(profileInitialSetupTokenFindOneSpy).toBeCalledTimes(1); - assertResMock(400, 'Missing location', response, mockRes); - }); - }); -}); diff --git a/src/controllers/titleController.js b/src/controllers/titleController.js index 3ca3175f5..08751bdef 100644 --- a/src/controllers/titleController.js +++ b/src/controllers/titleController.js @@ -1,203 +1,130 @@ const Team = require('../models/team'); const Project = require('../models/project'); const cacheClosure = require('../utilities/nodeCache'); -const userProfileController = require("./userProfileController"); -const userProfile = require('../models/userProfile'); -const project = require('../models/project'); - -const controller = userProfileController(userProfile, project); -const getAllTeamCodeHelper = controller.getAllTeamCodeHelper; +const { getAllTeamCodeHelper } = require("./userProfileController"); const titlecontroller = function (Title) { const cache = cacheClosure(); - const getAllTitles = function (req, res) { - Title.find({}) - .then((results) => res.status(200).send(results)) - .catch((error) => res.status(404).send(error)); + const getAllTitles = function (req, res) { + Title.find({}) + .then((results) => res.status(200).send(results)) + .catch((error) => res.status(404).send(error)); }; - const getTitleById = function (req, res) { - const { titleId } = req.params; - - Title.findById(titleId) - .then((results) => res.send(results)) - .catch((error) => res.send(error)); - }; - - const postTitle = async function (req, res) { - const title = new Title(); - title.titleName = req.body.titleName; - title.titleCode = req.body.titleCode; - title.teamCode = req.body.teamCode; - title.projectAssigned = req.body.projectAssigned; - title.mediaFolder = req.body.mediaFolder; - title.teamAssiged = req.body.teamAssiged; - - const titleCodeRegex = /^[A-Za-z]+$/; - if (!title.titleCode || !title.titleCode.trim()) { - return res.status(400).send({ message: 'Title code cannot be empty.' }); - } else if (!titleCodeRegex.test(title.titleCode)) { - return res.status(400).send({ message: 'Title Code must contain only upper or lower case letters.' }); - } - - // valid title name - if (!title.titleName.trim()) { - res.status(400).send({ message: 'Title cannot be empty.' }); - return; - } - - // if media is empty - if (!title.mediaFolder.trim()) { - res.status(400).send({ message: 'Media folder cannot be empty.' }); - return; - } - - if (!title.teamCode) { - res.status(400).send({ message: 'Please provide a team code.' }); - return; - } + const getTitleById = function (req, res) { + const { titleId } = req.params; - const teamCodeExists = await checkTeamCodeExists(title.teamCode); - if (!teamCodeExists) { - res.status(400).send({ message: 'Invalid team code. Please provide a valid team code.' }); - return; - } - - // validate if project exist - const projectExist = await checkProjectExists(title.projectAssigned._id); - if (!projectExist) { - res.status(400).send({ message: 'Project lalala is empty or not exist!!!' }); - return; - } - - // validate if team exist - if (title.teamAssiged && title.teamAssiged._id === 'N/A') { - res.status(400).send({ message: 'Team not exists.' }); - return; - } - - title - .save() - .then((results) => res.status(200).send(results)) - .catch((error) => res.status(404).send(error)) - }; + Title.findById(titleId) + .then((results) => res.send(results)) + .catch((error) => res.send(error)); + }; - // update title function. - const updateTitle = async function (req, res) { - try { + const postTitle = async function (req, res) { + const title = new Title(); - const filter = req.body.id; + title.titleName = req.body.titleName; + title.teamCode = req.body.teamCode; + title.projectAssigned = req.body.projectAssigned; + title.mediaFolder = req.body.mediaFolder; + title.teamAssiged = req.body.teamAssiged; // valid title name - if (!req.body.titleName.trim()) { + if (!title.titleName.trim()) { res.status(400).send({ message: 'Title cannot be empty.' }); return; } - if (!req.body.titleCode.trim()) { - res.status(400).send({ message: 'Title code cannot be empty.' }); - return; - } - - const titleCodeRegex = /^[A-Za-z]+$/; - if (!titleCodeRegex.test(req.body.titleCode)) { - return res.status(400).send({ message: 'Title Code must contain only upper or lower case letters.' }); - } - // if media is empty - if (!req.body.mediaFolder.trim()) { + if (!title.mediaFolder.trim()) { res.status(400).send({ message: 'Media folder cannot be empty.' }); return; } - if (!req.body.teamCode) { + const shortnames = title.titleName.trim().split(' '); + let shortname; + if (shortnames.length > 1) { + shortname = (shortnames[0][0] + shortnames[1][0]).toUpperCase(); + } else if (shortnames.length === 1) { + shortname = shortnames[0][0].toUpperCase(); + } + title.shortName = shortname; + + // Validate team code by checking if it exists in the database + if (!title.teamCode) { res.status(400).send({ message: 'Please provide a team code.' }); return; } - const teamCodeExists = await checkTeamCodeExists(req.body.teamCode); + const teamCodeExists = await checkTeamCodeExists(title.teamCode); if (!teamCodeExists) { res.status(400).send({ message: 'Invalid team code. Please provide a valid team code.' }); return; } // validate if project exist - const projectExist = await checkProjectExists(req.body.projectAssigned._id); + const projectExist = await checkProjectExists(title.projectAssigned._id); if (!projectExist) { - res.status(400).send({ message: 'Project is empty or not exist~~~' }); + res.status(400).send({ message: 'Project is empty or not exist.' }); return; } // validate if team exist - if (req.body.teamAssiged && req.body.teamAssiged._id === 'N/A') { + if (title.teamAssiged && title.teamAssiged._id === 'N/A') { res.status(400).send({ message: 'Team not exists.' }); return; } - const result = await Title.findById(filter); - result.titleName = req.body.titleName; - result.titleCode = req.body.titleCode; - result.teamCode = req.body.teamCode; - result.projectAssigned = req.body.projectAssigned; - result.mediaFolder = req.body.mediaFolder; - result.teamAssiged = req.body.teamAssiged; - const updatedTitle = await result.save(); - res.status(200).send({ message: 'Update successful', updatedTitle }); - - } catch (error) { - console.log(error); - res.status(500).send({ message: 'An error occurred', error }); - } - }; - - const deleteTitleById = async function (req, res) { - const { titleId } = req.params; - Title.deleteOne({ _id: titleId }) - .then((result) => res.send(result)) - .catch((error) => res.send(error)); - }; - - const deleteAllTitles = async function (req, res) { - Title.deleteMany({}) - .then((result) => { - if (result.deletedCount === 0) { - res.send({ message: 'No titles found to delete.' }); - } else { - res.send({ message: `${result.deletedCount} titles were deleted successfully.` }); + title + .save() + .then((results) => res.status(200).send(results)) + .catch((error) => res.status(404).send(error)); + }; + + const deleteTitleById = async function (req, res) { + const { titleId } = req.params; + Title.deleteOne({ _id: titleId }) + .then((result) => res.send(result)) + .catch((error) => res.send(error)); + }; + + const deleteAllTitles = async function (req, res) { + Title.deleteMany({}) + .then((result) => { + if (result.deletedCount === 0) { + res.send({ message: 'No titles found to delete.' }); + } else { + res.send({ message: `${result.deletedCount} titles were deleted successfully.` }); + } + }) + .catch((error) => { + res.status(500).send(error); + }); + }; + // Update: Confirmed with Jae. Team code is not related to the Team data model. But the team code field within the UserProfile data model. + async function checkTeamCodeExists(teamCode) { + try { + if (cache.getCache('teamCodes')) { + const teamCodes = JSON.parse(cache.getCache('teamCodes')); + return teamCodes.includes(teamCode); } - }) - .catch((error) => { - console.log(error) - res.status(500).send(error); - }); - }; - // Update: Confirmed with Jae. Team code is not related to the Team data model. But the team code field within the UserProfile data model. - async function checkTeamCodeExists(teamCode) { - try { - if (cache.getCache('teamCodes')) { - const teamCodes = JSON.parse(cache.getCache('teamCodes')); + const teamCodes = await getAllTeamCodeHelper(); return teamCodes.includes(teamCode); + } catch (error) { + console.error('Error checking if team code exists:', error); + throw error; } - const teamCodes = await getAllTeamCodeHelper(); - return teamCodes.includes(teamCode); - } catch (error) { - console.error('Error checking if team code exists:', error); - throw error; } - } - - async function checkProjectExists(projectID) { - try { - const project = await Project.findOne({ _id: projectID }).exec(); - return !!project; - } catch (error) { - console.error('Error checking if project exists:', error); - throw error; - } - } - + async function checkProjectExists(projectID) { + try { + const project = await Project.findOne({ _id: projectID }).exec(); + return !!project; + } catch (error) { + console.error('Error checking if project exists:', error); + throw error; + } + } return { getAllTitles, @@ -205,8 +132,8 @@ const titlecontroller = function (Title) { postTitle, deleteTitleById, deleteAllTitles, - updateTitle }; }; -module.exports = titlecontroller; + module.exports = titlecontroller; + \ No newline at end of file diff --git a/src/controllers/userProfileController.js b/src/controllers/userProfileController.js index 680dd93f4..8226aee65 100644 --- a/src/controllers/userProfileController.js +++ b/src/controllers/userProfileController.js @@ -196,38 +196,6 @@ const userProfileController = function (UserProfile, Project) { .catch((error) => res.status(404).send(error)); }; - /** - * Controller function to retrieve basic user profile information. - * This endpoint checks if the user has the necessary permissions to access user profiles. - * If authorized, it queries the database to fetch only the required fields: - * _id, firstName, lastName, isActive, startDate, and endDate, sorted by last name. - */ - const getUserProfileBasicInfo = async function (req, res) { - if (!(await checkPermission(req, 'getUserProfiles'))) { - forbidden(res, 'You are not authorized to view all users'); - return; - } - - await UserProfile.find({}, '_id firstName lastName isActive startDate createdDate endDate') - .sort({ - lastName: 1, - }) - .then((results) => { - if (!results) { - if (cache.getCache('allusers')) { - const getData = JSON.parse(cache.getCache('allusers')); - res.status(200).send(getData); - return; - } - res.status(500).send({ error: 'User result was invalid' }); - return; - } - cache.setCache('allusers', JSON.stringify(results)); - res.status(200).send(results); - }) - .catch((error) => res.status(404).send(error)); - }; - const getProjectMembers = async function (req, res) { if (!(await hasPermission(req.body.requestor, 'getProjectMembers'))) { res.status(403).send('You are not authorized to view all users'); @@ -358,7 +326,6 @@ const userProfileController = function (UserProfile, Project) { up.adminLinks = req.body.adminLinks; up.teams = Array.from(new Set(req.body.teams)); up.projects = Array.from(new Set(req.body.projects)); - up.teamCode = req.body.teamCode; up.createdDate = req.body.createdDate; up.startDate = req.body.startDate ? req.body.startDate : req.body.createdDate; up.email = req.body.email; @@ -538,7 +505,7 @@ const userProfileController = function (UserProfile, Project) { } }); - // Since we leverage cache for all team code retrival (refer func getAllTeamCode()), + // Since we leverage cache for all team code retrival (refer func getAllTeamCode()), // we need to remove the cache when team code is updated in case of new team code generation if (req.body.teamCode) { // remove teamCode cache when new team assigned @@ -677,7 +644,7 @@ const userProfileController = function (UserProfile, Project) { } if (req.body.startDate !== undefined && record.startDate !== req.body.startDate) { - record.startDate = moment.tz(req.body.startDate, 'America/Los_Angeles').toDate(); + record.startDate = moment(req.body.startDate).toDate(); // Make sure weeklycommittedHoursHistory isn't empty if (record.weeklycommittedHoursHistory.length === 0) { const newEntry = { @@ -700,7 +667,7 @@ const userProfileController = function (UserProfile, Project) { if (req.body.endDate !== undefined) { if (yearMonthDayDateValidator(req.body.endDate)) { - record.endDate = moment.tz(req.body.endDate, 'America/Los_Angeles').toDate(); + record.endDate = moment(req.body.endDate).toDate(); if (isUserInCache) { userData.endDate = record.endDate.toISOString(); } @@ -717,7 +684,12 @@ const userProfileController = function (UserProfile, Project) { userData.startDate = record.startDate.toISOString(); } } - + if ( + req.body.infringements !== undefined && + (await hasPermission(req.body.requestor, 'infringementAuthorizer')) + ) { + record.infringements = req.body.infringements; + } let updatedDiff = null; if (PROTECTED_EMAIL_ACCOUNT.includes(record.email)) { updatedDiff = record.modifiedPaths(); @@ -755,17 +727,7 @@ const userProfileController = function (UserProfile, Project) { 'update', ); }) - .catch((error) => { - if (error.name === 'ValidationError' && error.errors.lastName) { - const errors = Object.values(error.errors).map((er) => er.message); - return res.status(400).json({ - message: 'Validation Error', - error: errors, - }); - } - console.error('Failed to save record:', error); - return res.status(400).json({ error: 'Failed to save record.' }); - }); + .catch((error) => res.status(400).send(error)); }); }; @@ -881,11 +843,11 @@ const userProfileController = function (UserProfile, Project) { const getUserById = function (req, res) { const userid = req.params.userId; - // if (cache.getCache(`user-${userid}`)) { - // const getData = JSON.parse(cache.getCache(`user-${userid}`)); - // res.status(200).send(getData); - // return; - // } + if (cache.getCache(`user-${userid}`)) { + const getData = JSON.parse(cache.getCache(`user-${userid}`)); + res.status(200).send(getData); + return; + } UserProfile.findById(userid, '-password -refreshTokens -lastModifiedDate -__v') .populate([ @@ -915,15 +877,6 @@ const userProfileController = function (UserProfile, Project) { select: '_id badgeName type imageUrl description ranking showReport', }, }, - { - path: 'infringements', // Populate infringements field - select: 'date description', - options: { - sort: { - date: -1, // Sort by date descending if needed - }, - }, - }, ]) .exec() .then((results) => { @@ -1068,7 +1021,7 @@ const userProfileController = function (UserProfile, Project) { const hasUpdatePasswordPermission = await hasPermission(requestor, 'updatePassword'); // if they're updating someone else's password, they need the 'updatePassword' permission. - if (userId !== requestor.requestorId && !hasUpdatePasswordPermission) { + if (!hasUpdatePasswordPermission) { return res.status(403).send({ error: "You are unauthorized to update this user's password", }); @@ -1203,18 +1156,7 @@ const userProfileController = function (UserProfile, Project) { const activationDate = req.body.reactivationDate; const { endDate } = req.body; const isSet = req.body.isSet === 'FinalDay'; - let activeStatus = status; - let emailThreeWeeksSent = false; - if (endDate && status) { - const dateObject = new Date(endDate); - dateObject.setHours(dateObject.getHours() + 7); - const setEndDate = dateObject; - if (moment().isAfter(moment(setEndDate).add(1, 'days'))) { - activeStatus = false; - } else if (moment().isBefore(moment(endDate).subtract(3, 'weeks'))) { - emailThreeWeeksSent = true; - } - } + if (!mongoose.Types.ObjectId.isValid(userId)) { res.status(400).send({ error: 'Bad Request', @@ -1260,14 +1202,13 @@ const userProfileController = function (UserProfile, Project) { logger.logException(err, 'Unexpected error in finding menagement team'); } - UserProfile.findById(userId, 'isActive email firstName lastName finalEmailThreeWeeksSent') + UserProfile.findById(userId, 'isActive email firstName lastName') .then((user) => { user.set({ - isActive: activeStatus, + isActive: status, reactivationDate: activationDate, endDate, isSet, - finalEmailThreeWeeksSent: emailThreeWeeksSent, }); user .save() @@ -1291,8 +1232,6 @@ const userProfileController = function (UserProfile, Project) { user.email, recipients, isSet, - activationDate, - emailThreeWeeksSent, ); auditIfProtectedAccountUpdated( req.body.requestor.requestorId, @@ -1600,155 +1539,6 @@ const userProfileController = function (UserProfile, Project) { } }; - const addInfringements = async function (req, res) { - if (!(await hasPermission(req.body.requestor, 'addInfringements'))) { - res.status(403).send('You are not authorized to add blue square'); - return; - } - const userid = req.params.userId; - - cache.removeCache(`user-${userid}`); - - if (req.body.blueSquare === undefined) { - res.status(400).send('Invalid Data'); - return; - } - - UserProfile.findById(userid, async (err, record) => { - if (err || !record) { - res.status(404).send('No valid records found'); - return; - } - // find userData in cache - const isUserInCache = cache.hasCache('allusers'); - let allUserData; - let userData; - let userIdx; - if (isUserInCache) { - allUserData = JSON.parse(cache.getCache('allusers')); - userIdx = allUserData.findIndex((users) => users._id === userid); - userData = allUserData[userIdx]; - } - - const originalinfringements = record?.infringements ?? []; - record.infringements = originalinfringements.concat(req.body.blueSquare); - - record - .save() - .then((results) => { - userHelper.notifyInfringements( - originalinfringements, - results.infringements, - results.firstName, - results.lastName, - results.email, - results.role, - results.startDate, - results.jobTitle[0], - results.weeklycommittedHours, - ); - res.status(200).json({ - _id: record._id, - }); - - // update alluser cache if we have cache - if (isUserInCache) { - allUserData.splice(userIdx, 1, userData); - cache.setCache('allusers', JSON.stringify(allUserData)); - } - }) - .catch((error) => res.status(400).send(error)); - }); - }; - - const editInfringements = async function (req, res) { - if (!(await hasPermission(req.body.requestor, 'editInfringements'))) { - res.status(403).send('You are not authorized to edit blue square'); - return; - } - const { userId, blueSquareId } = req.params; - const { dateStamp, summary } = req.body; - - UserProfile.findById(userId, async (err, record) => { - if (err || !record) { - res.status(404).send('No valid records found'); - return; - } - - const originalinfringements = record?.infringements ?? []; - - record.infringements = originalinfringements.map((blueSquare) => { - if (blueSquare._id.equals(blueSquareId)) { - blueSquare.date = dateStamp ?? blueSquare.date; - blueSquare.description = summary ?? blueSquare.description; - } - return blueSquare; - }); - - record - .save() - .then((results) => { - userHelper.notifyInfringements( - originalinfringements, - results.infringements, - results.firstName, - results.lastName, - results.email, - results.role, - results.startDate, - results.jobTitle[0], - results.weeklycommittedHours, - ); - res.status(200).json({ - _id: record._id, - }); - }) - .catch((error) => res.status(400).send(error)); - }); - }; - - const deleteInfringements = async function (req, res) { - if (!(await hasPermission(req.body.requestor, 'deleteInfringements'))) { - res.status(403).send('You are not authorized to delete blue square'); - return; - } - const { userId, blueSquareId } = req.params; - // console.log(userId, blueSquareId); - - UserProfile.findById(userId, async (err, record) => { - if (err || !record) { - res.status(404).send('No valid records found'); - return; - } - - const originalinfringements = record?.infringements ?? []; - - record.infringements = originalinfringements.filter( - (infringement) => !infringement._id.equals(blueSquareId), - ); - - record - .save() - .then((results) => { - userHelper.notifyInfringements( - originalinfringements, - results.infringements, - results.firstName, - results.lastName, - results.email, - results.role, - results.startDate, - results.jobTitle[0], - results.weeklycommittedHours, - ); - res.status(200).json({ - _id: record._id, - }); - }) - .catch((error) => res.status(400).send(error)); - }); - }; - const getProjectsByPerson = async function (req, res) { try { const { name } = req.params; @@ -1807,74 +1597,21 @@ const userProfileController = function (UserProfile, Project) { return teamCodes; } const distinctTeamCodes = await UserProfile.distinct('teamCode', { - teamCode: { $ne: null }, + teamCode: { $ne: null } }); cache.setCache('teamCodes', JSON.stringify(distinctTeamCodes)); return distinctTeamCodes; } catch (error) { throw new Error('Encountered an error to get all team codes, please try again!'); } - }; + } const getAllTeamCode = async function (req, res) { try { const distinctTeamCodes = await getAllTeamCodeHelper(); return res.status(200).send({ message: 'Found', distinctTeamCodes }); } catch (error) { - return res - .status(500) - .send({ message: 'Encountered an error to get all team codes, please try again!' }); - } - }; - - const getUserByAutocomplete = (req, res) => { - const { searchText } = req.params; - - if (!searchText) { - return res.status(400).send({ message: 'Search text is required' }); - } - - const regex = new RegExp(searchText, 'i'); // Case-insensitive regex for partial matching - - UserProfile.find( - { - $or: [ - { firstName: { $regex: regex } }, - { lastName: { $regex: regex } }, - { - $expr: { - $regexMatch: { - input: { $concat: ['$firstName', ' ', '$lastName'] }, - regex: searchText, - options: 'i', - }, - }, - }, - ], - }, - '_id firstName lastName', // Projection to limit fields returned - ) - .limit(10) // Limit results for performance - .then((results) => { - res.status(200).send(results); - }) - .catch(() => { - res.status(500).send({ error: 'Internal Server Error' }); - }); - }; - - const updateUserInformation = async function (req,res){ - try { - const data=req.body; - data.map(async (e)=> { - let result = await UserProfile.findById(e.user_id); - result[e.item]=e.value - let newdata=await result.save() - }) - res.status(200).send({ message: 'Update successful'}); - } catch (error) { - console.log(error) - return res.status(500) + return res.status(500).send({ message: 'Encountered an error to get all team codes, please try again!' }); } } @@ -1900,16 +1637,9 @@ const userProfileController = function (UserProfile, Project) { getUserByFullName, changeUserRehireableStatus, authorizeUser, - addInfringements, - editInfringements, - deleteInfringements, getProjectsByPerson, getAllTeamCode, getAllTeamCodeHelper, - getUserByAutocomplete, - getUserProfileBasicInfo, - updateUserInformation, - getUserProfileBasicInfo }; }; diff --git a/src/cronjobs/userProfileJobs.js b/src/cronjobs/userProfileJobs.js index e7a8662a6..f0f69e146 100644 --- a/src/cronjobs/userProfileJobs.js +++ b/src/cronjobs/userProfileJobs.js @@ -19,16 +19,6 @@ const userProfileJobs = () => { } await userhelper.awardNewBadges(); await userhelper.reActivateUser(); - }, - null, - false, - 'America/Los_Angeles', - ); - - // Job to run every day, 1 minute past midnight to deactivate the user - const dailyUserDeactivateJobs = new CronJob( - '1 0 * * *', // Every day, 1 minute past midnight - async () => { await userhelper.deActivateUser(); }, null, @@ -37,6 +27,5 @@ const userProfileJobs = () => { ); allUserProfileJobs.start(); - dailyUserDeactivateJobs.start(); }; module.exports = userProfileJobs; diff --git a/src/helpers/dashboardhelper.js b/src/helpers/dashboardhelper.js index 08e0bc907..533dbe367 100644 --- a/src/helpers/dashboardhelper.js +++ b/src/helpers/dashboardhelper.js @@ -203,7 +203,6 @@ const dashboardhelper = function () { timeOffFrom: 1, timeOffTill: 1, endDate: 1, - missedHours: 1, } ); @@ -221,7 +220,7 @@ const dashboardhelper = function () { timeOffFrom: 1, timeOffTill: 1, endDate: 1, - missedHours: 1, + }, ); } @@ -270,7 +269,6 @@ const dashboardhelper = function () { ? teamMember.weeklySummaries[0].summary !== '' : false, weeklycommittedHours: teamMember.weeklycommittedHours, - missedHours: (teamMember.missedHours ?? 0), totaltangibletime_hrs: (timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds ?? 0) / 3600, totalintangibletime_hrs: diff --git a/src/helpers/taskHelper.js b/src/helpers/taskHelper.js index fefa9f021..34fb36be8 100644 --- a/src/helpers/taskHelper.js +++ b/src/helpers/taskHelper.js @@ -112,15 +112,9 @@ const taskHelper = function () { ); sharedTeamsResult.forEach((_myTeam) => { - let hasTeamVisibility = false; _myTeam.members.forEach((teamMember) => { - if (teamMember.userId.equals(userid) && teamMember.visible) hasTeamVisibility = true; + if (!teamMember.userId.equals(userid)) teamMemberIds.push(teamMember.userId); }); - if (hasTeamVisibility) { - _myTeam.members.forEach((teamMember) => { - if (!teamMember.userId.equals(userid)) teamMemberIds.push(teamMember.userId); - }); - } }); teamMembers = await userProfile diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 9ea062422..ed9c52131 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -656,7 +656,7 @@ const userHelper = function () { } // No extra hours is needed if blue squares isn't over 5. // length +1 is because new infringement hasn't been created at this stage. - const coreTeamExtraHour = Math.max(0, oldInfringements.length + 1 - 5); + const coreTeamExtraHour = Math.max(0, oldInfringements.length - 5); const utcStartMoment = moment(pdtStartOfLastWeek).add(1, 'second'); const utcEndMoment = moment(pdtEndOfLastWeek).subtract(1, 'day').subtract(1, 'second'); @@ -703,7 +703,7 @@ const userHelper = function () { .localeData() .ordinal( oldInfringements.length + 1, - )} blue square. So you should have completed ${weeklycommittedHours + coreTeamExtraHour} hours and you completed ${timeSpent.toFixed( + )} blue square. So you should have completed ${weeklycommittedHours} hours and you completed ${timeSpent.toFixed( 2, )} hours.`; } else { @@ -727,7 +727,7 @@ const userHelper = function () { .localeData() .ordinal( oldInfringements.length + 1, - )} blue square. So you should have completed ${weeklycommittedHours + coreTeamExtraHour} hours and you completed ${timeSpent.toFixed( + )} blue square. So you should have completed ${weeklycommittedHours} hours and you completed ${timeSpent.toFixed( 2, )} hours.`; } else { @@ -956,54 +956,29 @@ const userHelper = function () { $project: { _id: 1, missedHours: { - $let: { - vars: { - baseMissedHours: { - $max: [ - { - $subtract: [ - { - $sum: [{ $ifNull: ['$missedHours', 0] }, '$weeklycommittedHours'], - }, - { - $divide: [ - { - $sum: { - $map: { - input: '$timeEntries', - in: '$$this.totalSeconds', - }, - }, - }, - 3600, - ], + $max: [ + { + $subtract: [ + { + $sum: [{ $ifNull: ['$missedHours', 0] }, '$weeklycommittedHours'], + }, + { + $divide: [ + { + $sum: { + $map: { + input: '$timeEntries', + in: '$$this.totalSeconds', + }, }, - ], - }, - 0, - ], - }, - infringementsAdjustment: { - $cond: [ - { - $and: [ - { $gt: ['$infringements', null] }, - { $gt: [{ $size: '$infringements' }, 5] }, - ], - }, - { $subtract: [{ $size: '$infringements' }, 5] }, - 0, - ], - }, - }, - in: { - $cond: [ - { $gt: ['$$baseMissedHours', 0] }, - { $add: ['$$baseMissedHours', '$$infringementsAdjustment'] }, - '$$baseMissedHours', + }, + 3600, + ], + }, ], }, - }, + 0, + ], }, }, }, @@ -2062,78 +2037,36 @@ const userHelper = function () { email, recipients, isSet, - reactivationDate, - sendThreeWeeks, - followup, ) { - let subject; - let emailBody; - recipients.push('onecommunityglobal@gmail.com'); - recipients = recipients.toString(); - if (reactivationDate) { - subject = `IMPORTANT: ${firstName} ${lastName} has been PAUSED in the Highest Good Network`; - emailBody = `

Management,

- -

Please note that ${firstName} ${lastName} has been PAUSED in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.

-

For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part until they return on ${moment(reactivationDate).format('M-D-YYYY')}.

- -

With Gratitude,

- -

One Community

`; - emailSender(email, subject, emailBody, null, recipients, email); - } else if (endDate && isSet && sendThreeWeeks) { - const subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; + if (endDate && !isSet) { + const subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; const emailBody = `

Management,

- -

Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.

-

This is more than 3 weeks from now, but you should still start confirming all your work is being wrapped up with this individual and nothing further will be needed on their part after this date.

-

An additional reminder email will be sent in their final 2 weeks.

+

Please note that ${firstName} ${lastName} has been made inactive in the Highest Good Network as of ${endDate}. + Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.

With Gratitude,

One Community

`; - emailSender(email, subject, emailBody, null, recipients, email); + recipients.push('onecommunityglobal@gmail.com'); + recipients = recipients.toString(); + emailSender(recipients, subject, emailBody, null, null, email); + } else if (isSet) { + const subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; + const emailBody = `

Management,

- } else if (endDate && isSet && followup) { - subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; - emailBody = `

Management,

- -

Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.

-

This is coming up soon. For a smooth transition, please confirm all your work is wrapped up with this individual and nothing further will be needed on their part after this date.

- -

With Gratitude,

- -

One Community

`; - emailSender(email, subject, emailBody, null, recipients, email); - - } else if (endDate && isSet ) { - subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; - emailBody = `

Management,

- -

Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.

-

For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.

+

Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network ${endDate}. + For a smooth transition, please confirm all your work is being wrapped up with this individual and nothing further will be needed on their part after this date.

With Gratitude,

One Community

`; - emailSender(email, subject, emailBody, null, recipients, email); - - } else if(endDate){ - subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; - emailBody = `

Management,

- -

Please note that ${firstName} ${lastName} has been made inactive in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.

-

For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.

- -

With Gratitude,

- -

One Community

`; - emailSender(email, subject, emailBody, null, recipients, email); - }; - + recipients.push('onecommunityglobal@gmail.com'); + recipients = recipients.toString(); + emailSender(recipients, subject, emailBody, null, null, email); } - + }; + const deActivateUser = async () => { try { const emailReceivers = await userProfile.find( @@ -2143,38 +2076,13 @@ const userHelper = function () { const recipients = emailReceivers.map((receiver) => receiver.email); const users = await userProfile.find( { isActive: true, endDate: { $exists: true } }, - '_id isActive endDate isSet finalEmailThreeWeeksSent reactivationDate', + '_id isActive endDate isSet', ); for (let i = 0; i < users.length; i += 1) { const user = users[i]; - const { endDate, finalEmailThreeWeeksSent } = user; + const { endDate } = user; endDate.setHours(endDate.getHours() + 7); - // notify reminder set final day before 2 weeks - if(finalEmailThreeWeeksSent && moment().isBefore(moment(endDate).subtract(2, 'weeks')) && moment().isAfter(moment(endDate).subtract(3, 'weeks'))){ - const id = user._id; - const person = await userProfile.findById(id); - const lastDay = moment(person.endDate).format('YYYY-MM-DD'); - logger.logInfo(`User with id: ${user._id}'s final Day is set at ${moment().format()}.`); - person.teams.map(async (teamId) => { - const managementEmails = await userHelper.getTeamManagementEmail(teamId); - if (Array.isArray(managementEmails) && managementEmails.length > 0) { - managementEmails.forEach((management) => { - recipients.push(management.email); - }); - } - }); - sendDeactivateEmailBody( - person.firstName, - person.lastName, - lastDay, - person.email, - recipients, - person.isSet, - person.reactivationDate, - false, - true, - ); - } else if (moment().isAfter(moment(endDate).add(1, 'days'))) { + if (moment().isAfter(moment(endDate).add(1, 'days'))) { try { await userProfile.findByIdAndUpdate( user._id, @@ -2207,8 +2115,6 @@ const userHelper = function () { person.email, recipients, person.isSet, - person.reactivationDate, - undefined, ); } } diff --git a/src/models/jobs.js b/src/models/jobs.js deleted file mode 100644 index b1f98a34a..000000000 --- a/src/models/jobs.js +++ /dev/null @@ -1,17 +0,0 @@ -const mongoose = require('mongoose'); - -const { Schema } = mongoose; - -const jobSchema = new Schema({ - title: { type: String, required: true }, // Job title - category: { type: String, required: true }, // General category (e.g., Engineering, Marketing) - description: { type: String, required: true }, // Detailed job description - imageUrl: { type: String, required: true }, // URL of the job-related image - location: { type: String, required: true }, // Job location (optional for remote jobs) - applyLink: { type: String, required: true }, // URL for the application form - featured: { type: Boolean, default: false }, // Whether the job should be featured prominently - datePosted: { type: Date, default: Date.now }, // Date the job was posted - jobDetailsLink: { type: String, required: true }, // Specific job details URL -}); - -module.exports = mongoose.model('Job', jobSchema); diff --git a/src/models/team.js b/src/models/team.js index a679140a6..4d73615f5 100644 --- a/src/models/team.js +++ b/src/models/team.js @@ -15,10 +15,9 @@ const team = new Schema({ modifiedDatetime: { type: Date, default: Date.now() }, members: [ { - userId: { type: mongoose.SchemaTypes.ObjectId, required: true, index : true }, + userId: { type: mongoose.SchemaTypes.ObjectId, required: true }, addDateTime: { type: Date, default: Date.now(), ref: 'userProfile' }, visible: { type : 'Boolean', default:true}, - }, ], // Deprecated field @@ -36,5 +35,4 @@ const team = new Schema({ }, }); - module.exports = mongoose.model('team', team, 'teams'); diff --git a/src/models/timeentry.js b/src/models/timeentry.js index 1535ab13e..ea5303b3a 100644 --- a/src/models/timeentry.js +++ b/src/models/timeentry.js @@ -17,7 +17,5 @@ const TimeEntry = new Schema({ lastModifiedDateTime: { type: Date, default: Date.now }, isActive: { type: Boolean, default: true }, }); -TimeEntry.index({ personId: 1, dateOfWork: 1 }); -TimeEntry.index({ entryType: 1, teamId: 1, dateOfWork: 1, isActive: 1 }); module.exports = mongoose.model('timeEntry', TimeEntry, 'timeEntries'); diff --git a/src/models/title.js b/src/models/title.js index a41063aea..64b9aed92 100644 --- a/src/models/title.js +++ b/src/models/title.js @@ -4,7 +4,6 @@ const { Schema } = mongoose; const title = new Schema({ titleName: { type: String, required: true }, - titleCode: { type: String, required: true }, teamCode: { type: String, require: true }, projectAssigned: { projectName: { type: String, required: true }, @@ -14,7 +13,8 @@ const title = new Schema({ teamAssiged: { teamName: { type: String }, _id: { type: String }, - }, + }, + shortName: { type: String, require: true }, }); diff --git a/src/models/userProfile.js b/src/models/userProfile.js index 3a529294a..cc7136f54 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -27,7 +27,6 @@ const userProfileSchema = new Schema({ isActive: { type: Boolean, required: true, default: true }, isRehireable: { type: Boolean, default: true }, isSet: { type: Boolean, required: true, default: false }, - finalEmailThreeWeeksSent: { type: Boolean, required: true, default: false }, role: { type: String, required: true, diff --git a/src/routes/informationRouter.test.js b/src/routes/informationRouter.test.js deleted file mode 100644 index 12a600723..000000000 --- a/src/routes/informationRouter.test.js +++ /dev/null @@ -1,145 +0,0 @@ -const request = require('supertest'); -const { jwtPayload } = require('../test'); -const cache = require('../utilities/nodeCache')(); -const { app } = require('../app'); -const { - mockReq, - createUser, - mongoHelper: { dbConnect, dbDisconnect, dbClearCollections, dbClearAll }, -} = require('../test'); - -const agent = request.agent(app); - -describe('information routes', () => { - let user; - let token; - let reqBody = { - ...mockReq.body, - }; - beforeAll(async () => { - await dbConnect(); - user = await createUser(); - token = jwtPayload(user); - reqBody = { - ...reqBody, - infoName: 'some infoName', - infoContent: 'some infoContent', - visibility: '1', - }; - }); - beforeEach(async () => { - await dbClearCollections('informations'); - }); - - afterAll(async () => { - await dbClearAll(); - await dbDisconnect(); - }); - describe('informationRoutes', () => { - it('should return 401 if authorization header is not present', async () => { - await agent.post('/api/informations').send(reqBody).expect(401); - await agent.get('/api/informations/randomID').send(reqBody).expect(401); - }); - }); - describe('Post Information route', () => { - it('Should return 201 if the information is successfully added', async () => { - const response = await agent - .post('/api/informations') - .send(reqBody) - .set('Authorization', token) - .expect(201); - - expect(response.body).toEqual({ - _id: expect.anything(), - __v: expect.anything(), - infoName: reqBody.infoName, - infoContent: reqBody.infoContent, - visibility: reqBody.visibility, - }); - }); - }); - describe('Get Information route', () => { - it('Should return 201 if the information is successfully added', async () => { - const informations = [ - { - _id: '6605f860f948db61dab6f27m', - infoName: 'get info', - infoContent: 'get infoConten', - visibility: '1', - }, - ]; - cache.setCache('informations', JSON.stringify(informations)); - const response = await agent - .get('/api/informations') - .send(reqBody) - .set('Authorization', token) - .expect(200); - expect(response.body).toEqual({}); - }); - }); - describe('Delete Information route', () => { - it('Should return 400 if the route does not exist', async () => { - await agent - .delete('/api/informations/random123') - .send(reqBody) - .set('Authorization', token) - .expect(400); - }); - // thrown: "Exceeded timeout of 5000 ms for a test. - // Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." - // it('Should return 200 if deleting successfully', async () => { - // const _info = new Information(); - // _info.infoName = reqBody.infoName; - // _info.infoContent = reqBody.infoContent; - // _info.visibility = reqBody.visibility; - // const info = await _info.save(); - // const response = await agent - // .delete(`/api/informations/${info._id}`) - // .set('Authorization', token) - // .send(reqBody) - // .expect(200); - - // expect(response.body).toEqual( - // { - // _id: expect.anything(), - // __v: expect.anything(), - // infoName: info.infoName, - // infoContent: info.infoContent, - // visibility: info.visibility, - // }); - // }); - }); - describe('Update Information route', () => { - it('Should return 400 if the route does not exist', async () => { - await agent - .put('/api/informations/random123') - .send(reqBody) - .set('Authorization', token) - .expect(400); - }); - // thrown: "Exceeded timeout of 5000 ms for a test. - // Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." - // it('Should return 200 if udapted successfully', async () => { - // const _info = new Information(); - // _info.infoName = reqBody.infoName; - // _info.infoContent = reqBody.infoContent; - // _info.visibility = reqBody.visibility; - // const info = await _info.save(); - - // const response = await agent - // .put(`/api/informations/${info.id}`) - // .send(reqBody) - // .set('Authorization', token) - // .expect(200); - // expect(response.body).toEqual( - // { - // _id: expect.anything(), - // __v: expect.anything(), - // infoName: info.infoName, - // infoContent: info.infoContent, - // visibility: info.visibility, - // }); - - // }); - }); -}); diff --git a/src/routes/jobsRouter.js b/src/routes/jobsRouter.js deleted file mode 100644 index 5b66735a6..000000000 --- a/src/routes/jobsRouter.js +++ /dev/null @@ -1,14 +0,0 @@ -const express = require('express'); -const jobsController = require('../controllers/jobsController'); // Adjust the path if needed - -const router = express.Router(); - -// Define routes -router.get('/', jobsController.getJobs); -router.get('/categories', jobsController.getCategories); -router.get('/:id', jobsController.getJobById); -router.post('/', jobsController.createJob); -router.put('/:id', jobsController.updateJob); -router.delete('/:id', jobsController.deleteJob); - -module.exports = router; diff --git a/src/routes/reasonRouter.test.js b/src/routes/reasonRouter.test.js deleted file mode 100644 index a1f1ab6dc..000000000 --- a/src/routes/reasonRouter.test.js +++ /dev/null @@ -1,338 +0,0 @@ -const request = require('supertest'); -const moment = require('moment-timezone'); -const { jwtPayload } = require('../test'); -const cache = require('../utilities/nodeCache')(); -const { app } = require('../app'); -const { - mockReq, - mockUser, - createUser, - createTestPermissions, - mongoHelper: { dbConnect, dbDisconnect, dbClearCollections, dbClearAll }, -} = require('../test'); -// const Reason = require('../models/reason'); - -function mockDay(dayIdx, past = false) { - const date = moment().tz('America/Los_Angeles').startOf('day'); - while (date.day() !== dayIdx) { - date.add(past ? -1 : 1, 'days'); - } - return date; -} -const agent = request.agent(app); -describe('reason routers', () => { - let adminUser; - let adminToken; - let reqBody = { - body: { - ...mockReq.body, - ...mockUser(), - }, - }; - beforeAll(async () => { - await dbConnect(); - await createTestPermissions(); - adminUser = await createUser(); - adminToken = jwtPayload(adminUser); - }); - beforeEach(async () => { - await dbClearCollections('reason'); - await dbClearCollections('userProfiles'); - cache.setCache('allusers', '[]'); - reqBody = { - body: { - ...mockReq.body, - ...mockUser(), - reasonData: { - date: mockDay(0), - message: 'some reason', - }, - currentDate: moment.tz('America/Los_Angeles').startOf('day'), - }, - }; - }); - afterAll(async () => { - await dbClearAll(); - await dbDisconnect(); - }); - describe('reasonRouters', () => { - it('should return 401 if authorization header is not present', async () => { - await agent.post('/api/reason/').send(reqBody.body).expect(401); - await agent.get('/api/reason/randomId').send(reqBody.body).expect(401); - await agent.get('/api/reason/single/randomId').send(reqBody.body).expect(401); - await agent.patch('/api/reason/randomId/').send(reqBody.body).expect(401); - await agent.delete('/api/reason/randomId').send(reqBody.body).expect(401); - }); - }); - describe('Post reason route', () => { - it('Should return 400 if user did not choose SUNDAY', async () => { - reqBody.body.reasonData.date = mockDay(1, true); - const response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(400); - expect(response.body).toEqual({ - message: - "You must choose the Sunday YOU'LL RETURN as your date. This is so your reason ends up as a note on that blue square.", - errorCode: 0, - }); - }); - it('Should return 400 if warning to choose a future date', async () => { - reqBody.body.reasonData.date = mockDay(0, true); - const response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(400); - expect(response.body).toEqual({ - message: 'You should select a date that is yet to come', - errorCode: 7, - }); - }); - it('Should return 400 if not providing reason', async () => { - reqBody.body.reasonData.message = null; - const response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(400); - expect(response.body).toEqual({ - message: 'You must provide a reason.', - errorCode: 6, - }); - }); - it('Should return 404 if error in finding user Id', async () => { - reqBody.body.userId = null; - const response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(404); - expect(response.body).toEqual({ - message: 'User not found', - errorCode: 2, - }); - }); - it('Should return 403 if duplicate resonse', async () => { - // const userProfile = new userPro - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent - .get('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - expect(response.body).toBeTruthy(); - response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(403); - }); - it('Should return 200 if post successfully', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent - .get('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .post('/api/reason/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - }); - }); - describe('Get AllReason route', () => { - it('Should return 400 if route does not exist', async () => { - const response = await agent - .get(`/api/reason/random123`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(400); - expect(response.body).toEqual({ - errMessage: 'Something went wrong while fetching the user', - }); - }); - it('Should return 200 if get all reasons', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent - .get('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .get(`/api/reason/${userId}`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - }); - }); - describe('Get Single Reason route', () => { - it('Should return 400 if route does not exist', async () => { - reqBody.query = { - queryDate: mockDay(1, true), - }; - const response = await agent - .get(`/api/reason/single/5a7e21f00317bc1538def4b9`) - .set('Authorization', adminToken) - .expect(404); - expect(response.body).toEqual({ - message: 'User not found', - errorCode: 2, - }); - }); - it('Should return 200 if get all reasons', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent.get('/api/userProfile').set('Authorization', adminToken).expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - reqBody.query = { - queryDate: mockDay(1, true), - }; - response = await agent - .get(`/api/reason/single/${userId}`) - .set('Authorization', adminToken) - .expect(200); - }); - }); - describe('Patch reason route', () => { - it('Should return 404 if error in finding user Id', async () => { - reqBody.body.userId = null; - const response = await agent - .patch('/api/reason/5a7e21f00317bc1538def4b9/') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(404); - expect(response.body).toEqual({ - message: 'User not found', - errorCode: 2, - }); - }); - it('Should return 404 if duplicate reasons', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent.get('/api/userProfile').set('Authorization', adminToken).expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .patch(`/api/reason/${userId}/`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(404); - expect(response.body).toEqual({ - message: 'Reason not found', - errorCode: 4, - }); - }); - it('Should return 200 if patch successfully', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent.get('/api/userProfile').set('Authorization', adminToken).expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .post(`/api/reason/`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - expect(response.body).toBeTruthy(); - response = await agent - .patch(`/api/reason/${userId}/`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - expect(response.body).toEqual({ - message: 'Reason Updated!', - }); - }); - }); - describe('Delete reason route', () => { - it('Should return 404 if route does not exist', async () => { - const response = await agent - .delete(`/api/reason/5a7e21f00317bc1538def4b9`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(404); - expect(response.body).toEqual({ - message: 'User not found', - errorCode: 2, - }); - }); - it('Should return 200 if deleting successfully', async () => { - let response = await agent - .post('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - - expect(response.body).toBeTruthy(); - response = await agent - .get('/api/userProfile') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - const userId = response.body[0]._id; - reqBody.body.userId = userId; - response = await agent - .post(`/api/reason/`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - expect(response.body).toBeTruthy(); - response = await agent - .delete(`/api/reason/${userId}`) - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(200); - expect(response.body).toEqual({ - message: 'Document deleted', - }); - }); - }); -}); diff --git a/src/routes/teamRouter.js b/src/routes/teamRouter.js index 3fbd8abb5..1bf8cfc44 100644 --- a/src/routes/teamRouter.js +++ b/src/routes/teamRouter.js @@ -11,10 +11,6 @@ const router = function (team) { .post(controller.postTeam) .put(controller.updateTeamVisibility); - teamRouter - .route("/team/reports") - .post(controller.getAllTeamMembers); - teamRouter .route('/team/:teamId') .get(controller.getTeamById) diff --git a/src/routes/timeZoneAPIRoutes.test.js b/src/routes/timeZoneAPIRoutes.test.js deleted file mode 100644 index 975e56ac2..000000000 --- a/src/routes/timeZoneAPIRoutes.test.js +++ /dev/null @@ -1,208 +0,0 @@ -const request = require('supertest'); -const { jwtPayload } = require('../test'); - -const originalPremiumKey = process.env.TIMEZONE_PREMIUM_KEY; - -const { app } = require('../app'); -const { - mockReq, - mongoHelper: { dbConnect, dbDisconnect }, - createTestPermissions, - createUser, - mockUser, -} = require('../test'); - -const UserProfile = require('../models/userProfile'); -const ProfileInitialSetupToken = require('../models/profileInitialSetupToken'); - -const agent = request.agent(app); - -describe('timeZoneAPI routes', () => { - let adminUser; - let adminToken; - let volunteerUser; - let volunteerToken; - - const reqBody = {}; - const incorrectLocationParams = 'r'; - const locationParamsThatResultsInNoMatch = 'someReallyRandomLocation'; - const correctLocationParams = 'Berlin,+Germany'; - - beforeAll(async () => { - await dbConnect(); - await createTestPermissions(); - - reqBody.body = { - // This is the user we want to create - ...mockReq.body, - }; - adminUser = await createUser(); // This is the admin requestor user - adminToken = jwtPayload(adminUser); - - volunteerUser = mockUser(); // This is the admin requestor user - volunteerUser.email = 'volunteer@onecommunity.com'; - volunteerUser.role = 'Volunteer'; - volunteerUser = new UserProfile(volunteerUser); - volunteerUser = await volunteerUser.save(); - volunteerToken = jwtPayload(volunteerUser); - }); - - afterAll(async () => { - await dbDisconnect(); - - if (originalPremiumKey) { - process.env.TIMEZONE_PREMIUM_KEY = originalPremiumKey; - } else { - delete process.env.TIMEZONE_PREMIUM_KEY; - } - }); - - describe('API routes', () => { - it("should return 404 if route doesn't exist", async () => { - await agent - .post('/api/timezonesss') - .send(reqBody.body) - .set('Authorization', adminToken) - .expect(404); - }); - }); - - describe('getTimeZone - request parameter `location` based tests', () => { - test('401 when `API key` is missing', async () => { - const location = 'Berlin,+Germany'; - delete process.env.TIMEZONE_PREMIUM_KEY; - - const response = await agent - .get(`/api/timezone/${location}`) - .set('Authorization', adminToken) - .send(reqBody.body) - .expect(401); - - expect(response.error.text).toBe('API Key Missing'); - }); - - test('400 when `location` is incorrect', async () => { - const response = await agent - .get(`/api/timezone/${incorrectLocationParams}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(400); - - expect(response.error.text).toBeTruthy(); - }); - - test('200 when `location` is correctly formatted', async () => { - const response = await agent - .get(`/api/timezone/${correctLocationParams}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(200); - - expect(response).toBeTruthy(); - expect(response._body.timezone).toBeTruthy(); - expect(response._body.currentLocation).toBeTruthy(); - expect(response._body.currentLocation.userProvided).toBe(correctLocationParams); - }); - - test('404 when results.length === 0', async () => { - const response = await agent - .get(`/api/timezone/${locationParamsThatResultsInNoMatch}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(404); - - expect(response).toBeTruthy(); - }); - }); - - describe('getTimeZoneProfileInitialSetup - token is missing in body or in ProfileInitialSetupToken', () => { - test('401 when `token` is missing in request body', async () => { - const location = 'Berlin,+Germany'; - - const response = await agent - .post(`/api/timezone/${location}`) - .set('Authorization', adminToken) - .send(reqBody.body) - .expect(400); - - expect(response.error.text).toBe('Missing token'); - }); - - test('403 when ProfileInitialSetupToken does not contains `req.body.token`', async () => { - const location = 'Berlin,+Germany'; - reqBody.body = { - ...reqBody, - token: 'randomToken', - }; - - const response = await agent - .post(`/api/timezone/${location}`) - .set('Authorization', adminToken) - .send(reqBody.body) - .expect(403); - - expect(response.error.text).toBe('Unauthorized Request'); - }); - }); - - describe('getTimeZoneProfileInitialSetup - token is present in ProfileInitialSetupToken', () => { - const tokenData = 'randomToken'; - - beforeAll(async () => { - const expirationDate = new Date().setDate(new Date().getDate() + 10); - - let data = { - token: tokenData, - email: 'randomEmail', - weeklyCommittedHours: 5, - expiration: expirationDate, - createdDate: new Date(), - isCancelled: false, - isSetupCompleted: true, - }; - - data = new ProfileInitialSetupToken(data); - - // eslint-disable-next-line no-unused-vars - data = await data.save(); - - reqBody.body = { - ...reqBody, - token: tokenData, - }; - }); - - test('400 when `location` is incorrect', async () => { - const response = await agent - .get(`/api/timezone/${incorrectLocationParams}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(400); - - expect(response.error.text).toBeTruthy(); - }); - - test('200 when `location` is correctly formatted', async () => { - const response = await agent - .get(`/api/timezone/${correctLocationParams}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(200); - - expect(response).toBeTruthy(); - expect(response._body.timezone).toBeTruthy(); - expect(response._body.currentLocation).toBeTruthy(); - expect(response._body.currentLocation.userProvided).toBe(correctLocationParams); - }); - - test('404 when results.length === 0', async () => { - const response = await agent - .get(`/api/timezone/${locationParamsThatResultsInNoMatch}`) // Make sure this is the intended test - .set('Authorization', volunteerToken) - .send(reqBody.body) - .expect(404); - - expect(response).toBeTruthy(); - }); - }); -}); diff --git a/src/routes/timeentryRouter.js b/src/routes/timeentryRouter.js index b5fd641ae..0fd7db716 100644 --- a/src/routes/timeentryRouter.js +++ b/src/routes/timeentryRouter.js @@ -19,14 +19,6 @@ const routes = function (TimeEntry) { TimeEntryRouter.route('/TimeEntry/reports').post(controller.getTimeEntriesForReports); - TimeEntryRouter.route('/TimeEntry/reports/projects').post( - controller.getTimeEntriesForProjectReports, - ); - - TimeEntryRouter.route('/TimeEntry/reports/people').post( - controller.getTimeEntriesForPeopleReports, - ); - TimeEntryRouter.route('/TimeEntry/lostUsers').post(controller.getLostTimeEntriesForUserList); TimeEntryRouter.route('/TimeEntry/lostProjects').post( @@ -40,11 +32,9 @@ const routes = function (TimeEntry) { ); TimeEntryRouter.route('/TimeEntry/recalculateHoursAllUsers/tangible').post( - controller.startRecalculation, + controller.recalculateHoursByCategoryAllUsers, ); - TimeEntryRouter.route('/TimeEntry/checkStatus/:taskId').get(controller.checkRecalculationStatus); - TimeEntryRouter.route('/TimeEntry/recalculateHoursAllUsers/intangible').post( controller.recalculateIntangibleHrsAllUsers, ); diff --git a/src/routes/titleRouter.js b/src/routes/titleRouter.js index ce00e2279..f12cb5ec7 100644 --- a/src/routes/titleRouter.js +++ b/src/routes/titleRouter.js @@ -2,14 +2,12 @@ const express = require('express'); const router = function (title) { const controller = require('../controllers/titleController')(title); + const titleRouter = express.Router(); titleRouter.route('/title') .get(controller.getAllTitles) - .post(controller.postTitle) - // .put(controller.putTitle); - - titleRouter.route('/title/update').post(controller.updateTitle); + .post(controller.postTitle); titleRouter.route('/title/:titleId') .get(controller.getTitleById) diff --git a/src/routes/userProfileRouter.js b/src/routes/userProfileRouter.js index 2d68d2da1..bf6f79237 100644 --- a/src/routes/userProfileRouter.js +++ b/src/routes/userProfileRouter.js @@ -23,9 +23,6 @@ const routes = function (userProfile, project) { controller.postUserProfile, ); - userProfileRouter.route('/userProfile/update').patch(controller.updateUserInformation); - // Endpoint to retrieve basic user profile information - userProfileRouter.route('/userProfile/basicInfo').get(controller.getUserProfileBasicInfo); userProfileRouter .route('/userProfile/:userId') .get(controller.getUserById) @@ -105,21 +102,10 @@ const routes = function (userProfile, project) { .route('/userProfile/authorizeUser/weeeklySummaries') .post(controller.authorizeUser); - userProfileRouter.route('/userProfile/:userId/addInfringement').post(controller.addInfringements); - - userProfileRouter - .route('/userProfile/:userId/infringements/:blueSquareId') - .put(controller.editInfringements) - .delete(controller.deleteInfringements); - userProfileRouter.route('/userProfile/projects/:name').get(controller.getProjectsByPerson); userProfileRouter.route('/userProfile/teamCode/list').get(controller.getAllTeamCode); - userProfileRouter - .route('/userProfile/autocomplete/:searchText') - .get(controller.getUserByAutocomplete); - return userProfileRouter; }; diff --git a/src/startup/db.js b/src/startup/db.js index 719c17f94..c3c61807c 100644 --- a/src/startup/db.js +++ b/src/startup/db.js @@ -33,7 +33,7 @@ const afterConnect = async () => { module.exports = function () { const uri = `mongodb://${process.env.user}:${encodeURIComponent(process.env.password)}@${process.env.cluster}/${process.env.dbName}?ssl=true&replicaSet=${process.env.replicaSetName}&authSource=admin`; - + mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true, diff --git a/src/startup/middleware.js b/src/startup/middleware.js index 400f5af6c..eef3bd71b 100644 --- a/src/startup/middleware.js +++ b/src/startup/middleware.js @@ -10,8 +10,9 @@ module.exports = function (app) { } if ( - (req.originalUrl === '/api/login' || req.originalUrl === '/api/forgotpassword') && - req.method === 'POST' + (req.originalUrl === '/api/login' + || req.originalUrl === '/api/forgotpassword') + && req.method === 'POST' ) { next(); return; @@ -20,26 +21,12 @@ module.exports = function (app) { next(); return; } - if ( - ((req.originalUrl === '/api/ProfileInitialSetup' || - req.originalUrl === '/api/validateToken' || - req.originalUrl === '/api/getTimeZoneAPIKeyByToken') && - req.method === 'POST') || - (req.originalUrl === '/api/getTotalCountryCount' && req.method === 'GET') || - (req.originalUrl.includes('/api/timezone') && req.method === 'POST') + if (((req.originalUrl === '/api/ProfileInitialSetup' || req.originalUrl === '/api/validateToken' || req.originalUrl === '/api/getTimeZoneAPIKeyByToken') && req.method === 'POST') || (req.originalUrl === '/api/getTotalCountryCount' && req.method === 'GET') || (req.originalUrl.includes('/api/timezone') && req.method === 'POST') ) { next(); return; } - if ( - req.originalUrl === '/api/add-non-hgn-email-subscription' || - req.originalUrl === '/api/confirm-non-hgn-email-subscription' || - (req.originalUrl === '/api/remove-non-hgn-email-subscription' && req.method === 'POST') - ) { - next(); - return; - } - if (req.originalUrl.startsWith('/api/jobs') && req.method === 'GET') { + if (req.originalUrl === '/api/add-non-hgn-email-subscription' || req.originalUrl === '/api/confirm-non-hgn-email-subscription' || req.originalUrl === '/api/remove-non-hgn-email-subscription' && req.method === 'POST') { next(); return; } @@ -57,12 +44,13 @@ module.exports = function (app) { res.status(401).send('Invalid token'); return; } + if ( - !payload || - !payload.expiryTimestamp || - !payload.userid || - !payload.role || - moment().isAfter(payload.expiryTimestamp) + !payload + || !payload.expiryTimestamp + || !payload.userid + || !payload.role + || moment().isAfter(payload.expiryTimestamp) ) { res.status(401).send('Unauthorized request'); return; diff --git a/src/startup/routes.js b/src/startup/routes.js index b307ac4f4..82a4155a8 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -4,6 +4,7 @@ const project = require('../models/project'); const information = require('../models/information'); const team = require('../models/team'); // const actionItem = require('../models/actionItem'); +const notification = require('../models/notification'); const wbs = require('../models/wbs'); const task = require('../models/task'); const popup = require('../models/popupEditor'); @@ -54,7 +55,6 @@ const timeEntryRouter = require('../routes/timeentryRouter')(timeEntry); const projectRouter = require('../routes/projectRouter')(project); const informationRouter = require('../routes/informationRouter')(information); const teamRouter = require('../routes/teamRouter')(team); -const jobsRouter = require('../routes/jobsRouter'); // const actionItemRouter = require('../routes/actionItemRouter')(actionItem); const notificationRouter = require('../routes/notificationRouter')(); const loginRouter = require('../routes/loginRouter')(); @@ -162,7 +162,6 @@ module.exports = function (app) { app.use('/api', timeOffRequestRouter); app.use('/api', followUpRouter); app.use('/api', blueSquareEmailAssignmentRouter); - app.use('/api/jobs', jobsRouter) // bm dashboard app.use('/api/bm', bmLoginRouter); app.use('/api/bm', bmMaterialsRouter); diff --git a/src/test/createTestPermissions.js b/src/test/createTestPermissions.js index e0f9eddf1..58623ea3f 100644 --- a/src/test/createTestPermissions.js +++ b/src/test/createTestPermissions.js @@ -51,9 +51,7 @@ const permissionsRoles = [ 'changeUserStatus', 'updatePassword', 'deleteUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', // WBS 'postWbs', 'deleteWbs', @@ -113,9 +111,7 @@ const permissionsRoles = [ 'getUserProfiles', 'getProjectMembers', 'putUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', 'getReporteesLimitRoles', 'updateTask', 'putTeam', @@ -143,9 +139,7 @@ const permissionsRoles = [ 'getUserProfiles', 'getProjectMembers', 'putUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', 'getReporteesLimitRoles', 'getAllInvInProjectWBS', 'postInvInProjectWBS', @@ -200,8 +194,6 @@ const permissionsRoles = [ 'editTimeEntryToggleTangible', 'deleteTimeEntry', 'postTimeEntry', - 'sendEmails', - 'sendEmailToAll', 'updatePassword', 'getUserProfiles', 'getProjectMembers', @@ -210,9 +202,7 @@ const permissionsRoles = [ 'putUserProfileImportantInfo', 'updateSummaryRequirements', 'deleteUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', 'postWbs', 'deleteWbs', 'getAllInvInProjectWBS', diff --git a/src/utilities/createInitialPermissions.js b/src/utilities/createInitialPermissions.js index 062679cdd..43dfec2a0 100644 --- a/src/utilities/createInitialPermissions.js +++ b/src/utilities/createInitialPermissions.js @@ -52,9 +52,7 @@ const permissionsRoles = [ 'changeUserRehireableStatus', 'updatePassword', 'deleteUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', 'manageAdminLinks', 'manageTimeOffRequests', 'changeUserRehireableStatus', @@ -151,6 +149,7 @@ const permissionsRoles = [ { roleName: 'Mentor', permissions: [ + 'updateTask', 'suggestTask', 'putReviewStatus', 'getReporteesLimitRoles', @@ -213,8 +212,6 @@ const permissionsRoles = [ 'editTimeEntryToggleTangible', 'deleteTimeEntry', 'postTimeEntry', - 'sendEmails', - 'sendEmailToAll', 'updatePassword', 'getUserProfiles', 'getProjectMembers', @@ -223,9 +220,7 @@ const permissionsRoles = [ 'putUserProfileImportantInfo', 'updateSummaryRequirements', 'deleteUserProfile', - 'addInfringements', - 'editInfringements', - 'deleteInfringements', + 'infringementAuthorizer', 'postWbs', 'deleteWbs', 'getAllInvInProjectWBS', @@ -256,10 +251,7 @@ const permissionsRoles = [ 'seeUsersInDashboard', 'changeUserRehireableStatus', - - 'removeUserFromTask', - - 'editHeaderMessage', + 'manageAdminLinks', ], }, ]; diff --git a/src/utilities/emailSender.js b/src/utilities/emailSender.js index 839004e52..19834abf4 100644 --- a/src/utilities/emailSender.js +++ b/src/utilities/emailSender.js @@ -2,72 +2,57 @@ const nodemailer = require('nodemailer'); const { google } = require('googleapis'); const logger = require('../startup/logger'); -const config = { - email: process.env.REACT_APP_EMAIL, - clientId: process.env.REACT_APP_EMAIL_CLIENT_ID, - clientSecret: process.env.REACT_APP_EMAIL_CLIENT_SECRET, - redirectUri: process.env.REACT_APP_EMAIL_CLIENT_REDIRECT_URI, - refreshToken: process.env.REACT_APP_EMAIL_REFRESH_TOKEN, - batchSize: 50, - concurrency: 3, - rateLimitDelay: 1000, -}; - -const OAuth2Client = new google.auth.OAuth2( - config.clientId, - config.clientSecret, - config.redirectUri, -); -OAuth2Client.setCredentials({ refresh_token: config.refreshToken }); +const closure = () => { + const queue = []; -// Create the email envelope (transport) -const transporter = nodemailer.createTransport({ - service: 'gmail', - auth: { - type: 'OAuth2', - user: config.email, - clientId: config.clientId, - clientSecret: config.clientSecret, - }, -}); - -const sendEmail = async (mailOptions) => { - try { - const { token } = await OAuth2Client.getAccessToken(); - mailOptions.auth = { - user: config.email, - refreshToken: config.refreshToken, - accessToken: token, - }; - const result = await transporter.sendMail(mailOptions); - if (process.env.NODE_ENV === 'local') { - logger.logInfo(`Email sent: ${JSON.stringify(result)}`); - } - return result; - } catch (error) { - logger.logException(error, `Error sending email: ${mailOptions.to}`); - throw error; - } -}; + const CLIENT_EMAIL = process.env.REACT_APP_EMAIL; + const CLIENT_ID = process.env.REACT_APP_EMAIL_CLIENT_ID; + const CLIENT_SECRET = process.env.REACT_APP_EMAIL_CLIENT_SECRET; + const REDIRECT_URI = process.env.REACT_APP_EMAIL_CLIENT_REDIRECT_URI; + const REFRESH_TOKEN = process.env.REACT_APP_EMAIL_REFRESH_TOKEN; + // Create the email envelope (transport) + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + type: 'OAuth2', + user: CLIENT_EMAIL, + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, + }, + }); -const queue = []; -let isProcessing = false; + const OAuth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); -const { recipient, subject, message, cc, bcc, replyTo, acknowledgingReceipt, resolve, reject} = nextItem; + OAuth2Client.setCredentials({ refresh_token: REFRESH_TOKEN }); -const processQueue = async () => { - if (isProcessing || queue.length === 0) re + setInterval(async () => { + const nextItem = queue.shift(); - isProcessing = true; - console.log('Processing email queue...'); + if (!nextItem) return; - const processBatch = async () => { - if (queue.length === 0) { - isProcessing = false; - return; - } + const { recipient, subject, message, cc, bcc, replyTo, acknowledgingReceipt, resolve, reject} = nextItem; - const result = await transporter.sendMail(mailOptions); + try { + // Generate the accessToken on the fly + const res = await OAuth2Client.getAccessToken(); + const ACCESSTOKEN = res.token; + + const mailOptions = { + from: CLIENT_EMAIL, + to: recipient, + cc, + bcc, + subject, + html: message, + replyTo, + auth: { + user: CLIENT_EMAIL, + refreshToken: REFRESH_TOKEN, + accessToken: ACCESSTOKEN, + }, + }; + + const result = await transporter.sendMail(mailOptions); if (typeof acknowledgingReceipt === 'function') { acknowledgingReceipt(null, result); } @@ -91,14 +76,8 @@ const processQueue = async () => { `Extra Data: cc ${cc} bcc ${bcc}`, ); reject(error); - const batch = queue.shift(); - try { - console.log('Sending email...'); - await sendEmail(batch); - } catch (error) { - logger.logException(error, 'Failed to send email batch'); } - + }, process.env.MAIL_QUEUE_INTERVAL || 1000); const emailSender = function ( recipient, @@ -126,43 +105,9 @@ const processQueue = async () => { resolve('Email sending is disabled'); } }); - setTimeout(processBatch, config.rateLimitDelay); - }; - const concurrentProcesses = Array(config.concurrency).fill().map(processBatch); - - try { - await Promise.all(concurrentProcesses); - } finally { - isProcessing = false; - } -}; - -const emailSender = ( - recipients, - subject, - message, - attachments = null, - cc = null, - replyTo = null, -) => { - if (!process.env.sendEmail) return; - const recipientsArray = Array.isArray(recipients) ? recipients : [recipients]; - for (let i = 0; i < recipients.length; i += config.batchSize) { - const batchRecipients = recipientsArray.slice(i, i + config.batchSize); - queue.push({ - from: config.email, - bcc: batchRecipients.join(','), - subject, - html: message, - attachments, - cc, - replyTo, - }); - } - console.log('Emails queued:', queue.length); - setImmediate(processQueue); + return emailSender; }; -module.exports = emailSender; +module.exports = closure();