From 82451e5f4e64bb69a619c27b4b58472c9b99f27a Mon Sep 17 00:00:00 2001 From: Peter Harrison <16875803+palisadoes@users.noreply.github.com> Date: Sat, 27 Jul 2024 11:46:45 -0700 Subject: [PATCH] Latest Develop 20240724 (#2418) * 20240727112603 Deleted all files in the main branch in anticipation of merging develop into main cleanly * 20240727112834 Merge develop into main --- .github/workflows/pull_changes.yml | 23 - package-lock.json | 2264 ++++++++--------- package.json | 50 +- schema.graphql | 54 +- src/app.ts | 43 +- src/checks.ts | 24 +- src/config/appConfig.ts | 13 + src/config/index.ts | 4 + src/config/plugins/loadPlugins.ts | 37 +- src/constants.ts | 424 +-- src/db.ts | 10 +- .../authDirectiveTransformer.ts | 24 +- .../roleDirectiveTransformer.ts | 20 + .../createRecurringEvent.ts | 63 +- ...reateRecurringEventInstancesDuringQuery.ts | 64 +- .../createEventHelpers/createSingleEvent.ts | 41 +- src/helpers/event/createEventHelpers/index.ts | 15 + .../deleteRecurringEvent.ts | 45 +- .../deleteRecurringEventInstances.ts | 62 +- .../deleteEventHelpers/deleteSingleEvent.ts | 49 +- src/helpers/event/deleteEventHelpers/index.ts | 14 + .../createRecurrenceRule.ts | 34 +- .../generateRecurrenceRuleString.ts | 32 +- .../getRecurringInstanceDates.ts | 34 +- .../event/recurringEventHelpers/index.ts | 9 + .../removeDanglingDocuments.ts | 25 +- .../event/updateEventHelpers/getEventData.ts | 15 +- .../updateRecurringEvent.ts | 31 +- src/index.ts | 33 +- src/libraries/dbLogger.ts | 42 + .../errors/ImageSizeLimitExceeded.ts | 15 +- src/libraries/errors/applicationError.ts | 26 +- src/libraries/errors/conflictError.ts | 11 +- src/libraries/errors/inputValidationError.ts | 11 +- src/libraries/errors/internalServerError.ts | 15 +- src/libraries/errors/invalidFileTypeError.ts | 15 +- src/libraries/errors/notFoundError.ts | 11 +- src/libraries/errors/unauthenticatedError.ts | 15 +- src/libraries/errors/unauthorizedError.ts | 11 +- src/libraries/errors/validationError.ts | 11 +- src/libraries/index.ts | 7 + src/libraries/logger.ts | 32 +- src/libraries/requestContext.ts | 41 +- src/libraries/requestTracing.ts | 39 +- src/libraries/validators/compareDates.ts | 14 + src/libraries/validators/compareTime.ts | 14 + src/libraries/validators/validateString.ts | 7 + src/middleware/index.ts | 1 + src/middleware/isAuth.ts | 27 +- src/models/ActionItem.ts | 34 +- src/models/ActionItemCategory.ts | 22 +- src/models/Advertisement.ts | 11 +- src/models/AgendaCategory.ts | 14 +- src/models/AgendaItem.ts | 20 +- src/models/AgendaSection.ts | 15 +- src/models/AppUserProfile.ts | 24 +- src/models/CheckIn.ts | 19 +- src/models/CheckOut.ts | 23 +- src/models/Comment.ts | 28 +- src/models/Community.ts | 21 +- src/models/DirectChat.ts | 29 +- src/models/DirectChatMessage.ts | 26 +- src/models/Donation.ts | 26 +- src/models/EncodedImage.ts | 13 +- src/models/EncodedVideo.ts | 18 +- src/models/Event.ts | 57 +- src/models/EventAttendee.ts | 16 + src/models/EventVolunteer.ts | 33 +- src/models/EventVolunteerGroup.ts | 32 +- src/models/Feedback.ts | 30 +- src/models/File.ts | 36 +- src/models/Fund.ts | 45 +- src/models/FundraisingCampaign.ts | 55 +- src/models/FundraisingCampaignPledge.ts | 30 +- src/models/Group.ts | 32 +- src/models/GroupChat.ts | 40 +- src/models/GroupChatMessage.ts | 33 +- src/models/ImageHash.ts | 27 +- src/models/Language.ts | 41 +- src/models/MembershipRequest.ts | 17 +- src/models/Message.ts | 37 +- src/models/MessageChat.ts | 31 +- src/models/Note.ts | 16 + src/models/Organization.ts | 58 +- src/models/OrganizationCustomField.ts | 12 + src/models/OrganizationTagUser.ts | 27 +- src/models/Plugin.ts | 15 +- src/models/PluginField.ts | 25 +- src/models/Post.ts | 34 +- src/models/RecurrenceRule.ts | 14 +- src/models/SampleData.ts | 18 + src/models/TagUser.ts | 14 +- src/models/User.ts | 54 +- src/models/UserCustomData.ts | 14 +- src/models/Venue.ts | 8 +- src/models/userFamily.ts | 17 +- .../ActionItem/actionItemCategory.ts | 5 + src/resolvers/ActionItem/assignee.ts | 14 + src/resolvers/ActionItem/assigner.ts | 11 + src/resolvers/ActionItem/creator.ts | 20 + src/resolvers/ActionItem/event.ts | 24 + src/resolvers/ActionItemCategory/creator.ts | 11 + .../ActionItemCategory/organization.ts | 20 + src/resolvers/Advertisement/organization.ts | 11 + src/resolvers/AgendaCategory/createdBy.ts | 13 + src/resolvers/AgendaCategory/organization.ts | 12 + src/resolvers/AgendaCategory/updatedBy.ts | 15 + src/resolvers/AgendaItem/Users.ts | 12 + src/resolvers/AgendaItem/categories.ts | 14 +- src/resolvers/AgendaItem/createdBy.ts | 13 +- src/resolvers/AgendaItem/organization.ts | 13 + src/resolvers/AgendaItem/relatedEvent.ts | 12 + src/resolvers/AgendaItem/updatedBy.ts | 13 + src/resolvers/AgendaSection/createdBy.ts | 12 + src/resolvers/AgendaSection/index.ts | 11 + src/resolvers/AgendaSection/items.ts | 13 + src/resolvers/AgendaSection/relatedEvent.ts | 13 + src/resolvers/CheckIn/event.ts | 12 + src/resolvers/CheckIn/user.ts | 12 + src/resolvers/Comment/creator.ts | 12 + src/resolvers/DirectChat/index.ts | 12 + .../directChatMessageBelongsTo.ts | 14 +- src/resolvers/DirectChatMessage/receiver.ts | 14 +- src/resolvers/DirectChatMessage/sender.ts | 14 +- src/resolvers/Event/actionItems.ts | 14 +- src/resolvers/Event/attendees.ts | 12 + src/resolvers/Event/attendeesCheckInStatus.ts | 12 + src/resolvers/Event/averageFeedbackScore.ts | 12 + src/resolvers/Event/baseRecurringEvent.ts | 12 + src/resolvers/Event/creator.ts | 12 + src/resolvers/Event/feedback.ts | 12 + src/resolvers/Event/organization.ts | 12 + src/resolvers/Event/recurrenceRule.ts | 13 +- src/resolvers/EventVolunteer/creator.ts | 12 + src/resolvers/EventVolunteer/event.ts | 12 + src/resolvers/EventVolunteer/group.ts | 12 + src/resolvers/EventVolunteer/user.ts | 12 + src/resolvers/EventVolunteerGroup/creator.ts | 12 + src/resolvers/EventVolunteerGroup/event.ts | 12 + src/resolvers/EventVolunteerGroup/leader.ts | 12 + src/resolvers/Feedback/event.ts | 12 + src/resolvers/Fund/campaigns.ts | 12 - src/resolvers/Fund/creator.ts | 12 + src/resolvers/Fund/index.ts | 11 +- .../FundraisingCampagin/campaignPledges.ts | 17 - src/resolvers/FundraisingCampagin/index.ts | 8 - .../FundraisingCampagin/parentFund.ts | 16 - .../FundraisingCampaignPledge/index.ts | 1 + .../FundraisingCampaignPledge/users.ts | 13 +- src/resolvers/GroupChat/creator.ts | 14 +- src/resolvers/GroupChat/messages.ts | 14 +- src/resolvers/GroupChat/organization.ts | 14 +- src/resolvers/GroupChat/users.ts | 14 +- .../groupChatMessageBelongsTo.ts | 14 +- src/resolvers/GroupChatMessage/sender.ts | 14 +- .../MembershipRequest/organization.ts | 14 +- src/resolvers/MembershipRequest/user.ts | 14 +- src/resolvers/Mutation/addEventAttendee.ts | 27 + src/resolvers/Mutation/addFeedback.ts | 26 + .../Mutation/addLanguageTranslation.ts | 32 +- .../Mutation/addOrganizationCustomField.ts | 33 +- .../Mutation/addOrganizationImage.ts | 39 +- .../addPledgeToFundraisingCampaign.ts | 49 +- src/resolvers/Mutation/addUserCustomData.ts | 27 +- src/resolvers/Mutation/addUserImage.ts | 35 +- src/resolvers/Mutation/addUserToGroupChat.ts | 38 +- src/resolvers/Mutation/addUserToUserFamily.ts | 36 +- src/resolvers/Mutation/adminRemoveGroup.ts | 37 +- .../blockPluginCreationBySuperadmin.ts | 33 +- src/resolvers/Mutation/blockUser.ts | 44 +- .../Mutation/cancelMembershipRequest.ts | 38 +- src/resolvers/Mutation/createActionItem.ts | 58 +- .../Mutation/createActionItemCategory.ts | 38 +- src/resolvers/Mutation/createAdmin.ts | 106 +- src/resolvers/Mutation/createAdvertisement.ts | 23 + .../Mutation/createAgendaCategory.ts | 38 +- src/resolvers/Mutation/createAgendaItem.ts | 23 +- src/resolvers/Mutation/createAgendaSection.ts | 24 +- src/resolvers/Mutation/createComment.ts | 24 +- src/resolvers/Mutation/createDirectChat.ts | 63 +- src/resolvers/Mutation/createDonation.ts | 22 +- src/resolvers/Mutation/createEvent.ts | 38 +- .../Mutation/createEventVolunteer.ts | 33 +- .../Mutation/createEventVolunteerGroup.ts | 31 +- src/resolvers/Mutation/createFund.ts | 50 +- .../Mutation/createFundraisingCampaign.ts | 46 +- .../createFundraisingCampaignPledge.ts | 44 +- src/resolvers/Mutation/createGroupChat.ts | 37 +- src/resolvers/Mutation/createMember.ts | 73 +- src/resolvers/Mutation/createMessageChat.ts | 32 +- src/resolvers/Mutation/createNote.ts | 25 +- src/resolvers/Mutation/createOrganization.ts | 51 +- src/resolvers/Mutation/createPlugin.ts | 24 +- src/resolvers/Mutation/createPost.ts | 43 +- .../Mutation/createSampleOrganization.ts | 16 +- src/resolvers/Mutation/createUserFamily.ts | 37 +- src/resolvers/Mutation/createUserTag.ts | 23 +- src/resolvers/Mutation/createVenue.ts | 37 +- .../removeFundraisingCampaingPledge.ts | 11 - src/resolvers/Mutation/updateAgendaItem.ts | 23 +- .../Mutation/updateFundCampaignPledge.ts | 34 +- .../Organization/actionItemCategories.ts | 14 +- src/resolvers/Organization/admins.ts | 14 +- src/resolvers/Organization/advertisements.ts | 43 +- .../Organization/agendaCategories.ts | 14 +- src/resolvers/Organization/blockedUsers.ts | 14 +- src/resolvers/Organization/creator.ts | 14 +- src/resolvers/Organization/funds.ts | 13 +- src/resolvers/Organization/image.ts | 11 + src/resolvers/Organization/index.ts | 3 +- src/resolvers/Organization/members.ts | 13 +- .../Organization/membershipRequests.ts | 15 +- src/resolvers/Organization/pinnedPosts.ts | 12 + src/resolvers/Organization/posts.ts | 38 +- src/resolvers/Organization/userTags.ts | 152 ++ src/resolvers/Organization/venues.ts | 14 +- src/resolvers/Post/comments.ts | 12 + src/resolvers/Post/creator.ts | 12 + src/resolvers/Query/agendaItemByEvent.ts | 19 + .../Query/agendaItemByOrganization.ts | 15 + src/resolvers/Query/directChatById.ts | 31 + src/resolvers/Query/fundsByOrganization.ts | 4 +- src/resolvers/Query/getFundById.ts | 16 +- src/resolvers/Query/getFundraisingCampaign.ts | 15 +- src/resolvers/Query/groupChatById.ts | 31 + src/resolvers/Query/groupChatsByUserId.ts | 30 + .../Query/helperFunctions/getSort.ts | 30 + .../Query/helperFunctions/getWhere.ts | 2 + src/resolvers/Query/index.ts | 10 + .../RecurrenceRule/baseRecurringEvent.ts | 12 + src/resolvers/RecurrenceRule/organization.ts | 13 + .../Subscription/messageSentToDirectChat.ts | 15 +- .../Subscription/messageSentToGroupChat.ts | 13 +- src/resolvers/Subscription/onPluginUpdate.ts | 16 + src/resolvers/UserFamily/admins.ts | 14 +- src/resolvers/UserFamily/creator.ts | 14 +- src/resolvers/UserFamily/users.ts | 14 +- src/resolvers/UserTag/childTags.ts | 19 + src/resolvers/UserTag/organization.ts | 12 + src/resolvers/UserTag/parentTag.ts | 12 + src/resolvers/UserTag/usersAssignedTo.ts | 40 +- src/resolvers/index.ts | 2 - src/resolvers/middleware/currentUserExists.ts | 14 +- .../cacheAppUserProfile.ts | 17 +- .../deleteAppUserFromCache.ts | 9 + src/services/CommentCache/cacheComments.ts | 22 +- .../CommentCache/deleteCommentFromCache.ts | 8 + src/services/EventCache/cacheEvents.ts | 16 +- .../EventCache/deleteEventFromCache.ts | 8 + .../OrganizationCache/cacheOrganizations.ts | 16 +- .../deleteOrganizationFromCache.ts | 8 + src/services/PostCache/cachePosts.ts | 20 +- src/services/PostCache/deletePostFromCache.ts | 7 + src/services/UserCache/cacheUser.ts | 19 +- src/services/UserCache/deleteUserFromCache.ts | 3 + src/services/UserCache/findUserInCache.ts | 17 + src/services/redisCache.ts | 7 +- src/typeDefs/directives.ts | 5 + src/typeDefs/enums.ts | 23 + src/typeDefs/errors/common.ts | 3 + src/typeDefs/errors/connectionError.ts | 3 + src/typeDefs/errors/createAdminErrors.ts | 3 + src/typeDefs/errors/createCommentErrors.ts | 4 + src/typeDefs/errors/createDirectChatError.ts | 4 + src/typeDefs/errors/createMemberErrors.ts | 4 + src/typeDefs/errors/index.ts | 4 + src/typeDefs/inputs.ts | 10 +- src/typeDefs/queries.ts | 27 +- src/typeDefs/subscriptions.ts | 4 +- src/typeDefs/types.ts | 6 +- src/types/generatedGraphQLTypes.ts | 104 +- src/utilities/PII/decryption.ts | 18 +- src/utilities/PII/encryption.ts | 21 +- src/utilities/PII/isAuthorised.ts | 12 +- src/utilities/adminCheck.ts | 26 +- src/utilities/auth.ts | 25 +- src/utilities/checkReplicaSet.ts | 7 + src/utilities/copyToClipboard.ts | 9 +- src/utilities/createSampleOrganizationUtil.ts | 49 + src/utilities/dateValidator.ts | 23 +- src/utilities/deleteDuplicatedImage.ts | 10 +- src/utilities/deleteImage.ts | 24 +- .../deletePreviousImage.ts | 9 + .../encodedImageExtensionCheck.ts | 6 + .../encodedImageStorage/uploadEncodedImage.ts | 37 +- .../deletePreviousVideo.ts | 12 + .../encodedVideoExtensionCheck.ts | 13 +- .../encodedVideoStorage/uploadEncodedVideo.ts | 22 +- src/utilities/imageAlreadyInDbCheck.ts | 24 +- src/utilities/imageExtensionCheck.ts | 11 +- src/utilities/loadDefaultOrg.ts | 14 + src/utilities/loadSampleData.ts | 13 + src/utilities/mailer.ts | 29 +- src/utilities/removeSampleOrganizationUtil.ts | 12 + src/utilities/reuploadDuplicateCheck.ts | 36 +- src/utilities/superAdminCheck.ts | 6 + src/utilities/uploadImage.ts | 17 +- src/utilities/userFamilyAdminCheck.ts | 20 +- tests/helpers/directChat.ts | 2 +- tests/helpers/groupChat.ts | 2 +- tests/resolvers/Fund/campaign.spec.ts | 29 - .../FundraisingCampaign/parentfund.spec.ts | 34 - .../FundraisingCampaign/pledge.spec.ts | 33 - .../Mutation/createDirectChat.spec.ts | 1 - tests/resolvers/Mutation/createFund.spec.ts | 28 +- .../Mutation/removeFundCampaignPledge.spec.ts | 19 +- .../Mutation/updateAgendaItem.spec.ts | 8 +- .../Mutation/updateFundCampaignPledge.spec.ts | 52 +- tests/resolvers/Organization/userTags.spec.ts | 118 + .../resolvers/Query/agendaItemByEvent.spec.ts | 35 + .../Query/agendaItemByOrganization.spec.ts | 39 + tests/resolvers/Query/directChatById.spec.ts | 58 + .../Query/getFundCampaignById.spec.ts | 8 +- tests/resolvers/Query/groupChatById.spec.ts | 61 + .../Query/groupChatsByUserId.spec.ts | 58 + .../Query/helperFunctions/getSort.spec.ts | 16 +- .../Query/helperFunctions/getWhere.spec.ts | 2 + .../messageSentToDirectChat.spec.ts | 12 +- .../messageSentToGroupChat.spec.ts | 10 +- tests/resolvers/User/post.spec.ts | 166 +- 320 files changed, 6773 insertions(+), 3309 deletions(-) delete mode 100644 .github/workflows/pull_changes.yml delete mode 100644 src/resolvers/Fund/campaigns.ts delete mode 100644 src/resolvers/FundraisingCampagin/campaignPledges.ts delete mode 100644 src/resolvers/FundraisingCampagin/index.ts delete mode 100644 src/resolvers/FundraisingCampagin/parentFund.ts create mode 100644 src/resolvers/Organization/userTags.ts create mode 100644 src/resolvers/Query/agendaItemByEvent.ts create mode 100644 src/resolvers/Query/agendaItemByOrganization.ts create mode 100644 src/resolvers/Query/directChatById.ts create mode 100644 src/resolvers/Query/groupChatById.ts create mode 100644 src/resolvers/Query/groupChatsByUserId.ts delete mode 100644 tests/resolvers/Fund/campaign.spec.ts delete mode 100644 tests/resolvers/FundraisingCampaign/parentfund.spec.ts delete mode 100644 tests/resolvers/FundraisingCampaign/pledge.spec.ts create mode 100644 tests/resolvers/Organization/userTags.spec.ts create mode 100644 tests/resolvers/Query/agendaItemByEvent.spec.ts create mode 100644 tests/resolvers/Query/agendaItemByOrganization.spec.ts create mode 100644 tests/resolvers/Query/directChatById.spec.ts create mode 100644 tests/resolvers/Query/groupChatById.spec.ts create mode 100644 tests/resolvers/Query/groupChatsByUserId.spec.ts diff --git a/.github/workflows/pull_changes.yml b/.github/workflows/pull_changes.yml deleted file mode 100644 index b320774a7a..0000000000 --- a/.github/workflows/pull_changes.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Copy Docs to Talawa Docs - -on: - schedule: - - cron: '*/5 * * * *' -jobs: - copy-docs-to-talawa-docs: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/automated-docs' - # needs: Generate-Documentation - steps: - - uses: actions/checkout@v4 - - uses: dmnemec/copy_file_to_another_repo_action@v1.1.1 - env: - API_TOKEN_GITHUB: ${{secrets.TALAWA_DOCS_SYNC}} - with: - source_file: 'talawa-api-docs/' - destination_repo: 'PalisadoesFoundation/talawa-docs' - destination_branch: 'develop' - destination_folder: 'docs/' - user_email: '${{env.email}}' - user_name: '${{github.actor}}' - commit_message: 'Talawa API docs updated' diff --git a/package-lock.json b/package-lock.json index d58026a34a..172156bac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "dependencies": { "@apollo/server": "^4.10.4", "@faker-js/faker": "^8.2.0", - "@graphql-inspector/cli": "^4.0.3", + "@graphql-inspector/cli": "^5.0.6", "@graphql-tools/resolvers-composition": "^7.0.1", - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.2.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.3.2", "@parcel/watcher": "^2.4.1", "@types/graphql-upload": "^16.0.5", "@types/yargs": "^17.0.32", @@ -29,8 +29,8 @@ "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.2.0", - "graphql": "^16.8.1", + "express-rate-limit": "^7.3.1", + "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", "graphql-subscriptions": "^2.0.0", @@ -46,28 +46,28 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "markdown-toc": "^1.2.0", - "mongodb": "^6.3.0", + "mongodb": "^6.7.0", "mongoose": "^8.3.2", - "mongoose-paginate-v2": "^1.8.1", + "mongoose-paginate-v2": "^1.8.2", "morgan": "^1.10.0", "nanoid": "^5.0.7", - "nodemailer": "^6.9.12", - "pm2": "^5.2.0", - "redis": "^4.6.14", + "nodemailer": "^6.9.14", + "pm2": "^5.4.0", + "redis": "^4.6.15", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.0.1", - "uuid": "^9.0.0", + "typedoc-plugin-markdown": "^4.2.1", + "uuid": "^10.0.0", "validator": "^13.12.0", "winston": "^3.13.0", - "ws": "^8.17.0", + "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", "zod-error": "^1.5.0" }, "devDependencies": { "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.7", - "@graphql-codegen/typescript-resolvers": "^4.1.0", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-resolvers": "^4.2.0", "@graphql-eslint/eslint-plugin": "^3.20.1", "@parcel/watcher": "^2.4.1", "@types/bcryptjs": "^2.4.6", @@ -80,29 +80,29 @@ "@types/i18n": "^0.13.12", "@types/inquirer": "^9.0.7", "@types/jsonwebtoken": "^9.0.5", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.6", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^20.12.7", + "@types/node": "^20.14.9", "@types/nodemailer": "^6.4.15", "@types/uuid": "^9.0.7", - "@types/validator": "^13.11.10", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@types/validator": "^13.12.0", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.16.0", "@vitest/coverage-v8": "^1.6.0", "cls-bluebird": "^2.1.0", "concurrently": "^8.2.2", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-tsdoc": "^0.2.17", + "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", "graphql-markdown": "^7.0.0", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", + "husky": "^9.1.1", + "lint-staged": "^15.2.7", "prettier": "^3.2.5", - "rimraf": "^5.0.7", - "tsx": "^4.7.3", + "rimraf": "^6.0.1", + "tsx": "^4.16.2", "typescript": "^5.4.5", "vitest": "^1.2.1" } @@ -250,6 +250,18 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@apollo/server/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@apollo/usage-reporting-protobuf": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", @@ -538,11 +550,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.6", "picocolors": "^1.0.0" }, "engines": { @@ -550,32 +562,32 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", - "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", + "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", + "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-compilation-targets": "^7.24.6", + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helpers": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/template": "^7.24.6", + "@babel/traverse": "^7.24.6", + "@babel/types": "^7.24.6", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { @@ -586,12 +598,17 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, "node_modules/@babel/generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", - "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", + "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", "dependencies": { - "@babel/types": "^7.24.0", + "@babel/types": "^7.24.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -613,12 +630,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", + "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -664,31 +681,31 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", + "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -707,26 +724,26 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -773,11 +790,11 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -796,59 +813,58 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", + "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", + "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -922,9 +938,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1369,31 +1385,31 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", + "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", + "dependencies": { + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-hoist-variables": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1402,12 +1418,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", + "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1594,32 +1610,32 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@envelop/core": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@envelop/core/-/core-4.0.3.tgz", - "integrity": "sha512-O0Vz8E0TObT6ijAob8jYFVJavcGywKThM3UAsxUIBBVPYZTMiqI9lo2gmAnbMUnrDcAYkUTZEW9FDYPRdF5l6g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.1.tgz", + "integrity": "sha512-wxA8EyE1fPnlbP0nC/SFI7uU8wSNf4YjxZhAPu0P63QbgIvqHtHsH4L3/u+rsTruzhk3OvNRgQyLsMfaR9uzAQ==", "dependencies": { - "@envelop/types": "4.0.1", + "@envelop/types": "5.0.0", "tslib": "^2.5.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@envelop/types": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@envelop/types/-/types-4.0.1.tgz", - "integrity": "sha512-ULo27/doEsP7uUhm2iTnElx13qTO6I5FKvmLoX41cpfuw8x6e0NUFknoqhEsLzAbgz8xVS5mjwcxGCXh4lDYzg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.0.0.tgz", + "integrity": "sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==", "dependencies": { "tslib": "^2.5.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -1633,9 +1649,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -1649,9 +1665,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -1665,9 +1681,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -1681,9 +1697,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -1697,9 +1713,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -1713,9 +1729,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -1729,9 +1745,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -1745,9 +1761,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -1761,9 +1777,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -1777,9 +1793,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -1793,9 +1809,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -1809,9 +1825,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -1825,9 +1841,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -1841,9 +1857,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -1857,9 +1873,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -1873,9 +1889,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -1889,9 +1905,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -1905,9 +1921,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -1921,9 +1937,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -1937,9 +1953,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -1953,9 +1969,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -1969,9 +1985,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -2278,14 +2294,14 @@ } }, "node_modules/@graphql-codegen/typescript": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.7.tgz", - "integrity": "sha512-Gn+JNvQBJhBqH7s83piAJ6UeU/MTj9GXWFO9bdbl8PMLCAM1uFAtg04iHfkGCtDKXcUg5a3Dt/SZG85uk5KuhA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.9.tgz", + "integrity": "sha512-0O35DMR4d/ctuHL1Zo6mRUUzp0BoszKfeWsa6sCm/g70+S98+hEfTwZNDkQHylLxapiyjssF9uw/F+sXqejqLw==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", "@graphql-codegen/schema-ast": "^4.0.2", - "@graphql-codegen/visitor-plugin-common": "5.2.0", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2310,14 +2326,14 @@ } }, "node_modules/@graphql-codegen/typescript-resolvers": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.1.0.tgz", - "integrity": "sha512-JKosVjsZHaGfXIllWxuPPJ9DsAh72GVuyB+IFU3jNoM2sXuSNJsBVIT0CzpsxZr0rdkpcY6FfG2sS3zpE/TQrQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.2.0.tgz", + "integrity": "sha512-zy/H3T2RTT3PEkNS3CSdgF1lFrRsbHXu1WAIUhknmtsQugNBE2B5rbdPx8Wdat7QxtReSEEQ54Tgl9F87ecqyg==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/typescript": "^4.0.7", - "@graphql-codegen/visitor-plugin-common": "5.2.0", + "@graphql-codegen/typescript": "^4.0.8", + "@graphql-codegen/visitor-plugin-common": "5.3.0", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -2327,9 +2343,9 @@ } }, "node_modules/@graphql-codegen/typescript-resolvers/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.2.0.tgz", - "integrity": "sha512-0p8AwmARaZCAlDFfQu6Sz+JV6SjbPDx3y2nNM7WAAf0au7Im/GpJ7Ke3xaIYBc1b2rTZ+DqSTJI/zomENGD9NA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.0.tgz", + "integrity": "sha512-+kUk7gRD/72Wfkjd7D96Lonh9k4lFw9d3O1+I07Jyja4QN9H42kdFEO0hM/b4Q9lLkI1yJ66Oym7lWz2Ikj3aw==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", @@ -2348,9 +2364,9 @@ } }, "node_modules/@graphql-codegen/typescript/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.2.0.tgz", - "integrity": "sha512-0p8AwmARaZCAlDFfQu6Sz+JV6SjbPDx3y2nNM7WAAf0au7Im/GpJ7Ke3xaIYBc1b2rTZ+DqSTJI/zomENGD9NA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", + "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", "dev": true, "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", @@ -2790,30 +2806,31 @@ } }, "node_modules/@graphql-inspector/audit-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/audit-command/-/audit-command-4.0.3.tgz", - "integrity": "sha512-cm4EtieIp9PUSDBze+Sn5HHF80jDF9V7sYyXqFa7+Vtw4Jlet98Ig48dFVtoLuFCPtCv2eZ22I8JOkBKL5WgVA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/audit-command/-/audit-command-5.0.6.tgz", + "integrity": "sha512-XfQIKoQj9TTNjEQOKUzTXVhjdJ97zJAqQi4M17OiYKsS8HpiSTmbPEzs+3GcJ/B3OZz+BtW4Oh0OGHTgImDzuw==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", - "@graphql-tools/utils": "10.0.3", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", "cli-table3": "0.6.3", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/audit-command/node_modules/@graphql-tools/utils": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.3.tgz", - "integrity": "sha512-6uO41urAEIs4sXQT2+CYGsUTkHkVo/2MpM/QjoHj6D6xoEF2woXHBpdAVi0HKIInDwZqWgEYOwIFez0pERxa1Q==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", "dset": "^3.1.2", "tslib": "^2.4.0" }, @@ -2825,28 +2842,28 @@ } }, "node_modules/@graphql-inspector/cli": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/cli/-/cli-4.0.3.tgz", - "integrity": "sha512-54pJ/SkFGz/qKEP2sjiCqxTG6QkWSzG4NdfKqhlinQstlyCgtbmgwgh90fUPoG6yOgZZhcsiSJXHkz255kRu4w==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/cli/-/cli-5.0.6.tgz", + "integrity": "sha512-yawOs8fY9AC6eBeeH1CNw1DyAQmBe9dNvAMjS+bG1k9haSn7erzfeWWkoPbqYcFty6FWLlHwVwA00nfpOdJhzQ==", "dependencies": { - "@babel/core": "7.22.9", - "@graphql-inspector/audit-command": "4.0.3", - "@graphql-inspector/code-loader": "4.0.2", - "@graphql-inspector/commands": "4.0.3", + "@babel/core": "7.24.6", + "@graphql-inspector/audit-command": "5.0.6", + "@graphql-inspector/code-loader": "5.0.1", + "@graphql-inspector/commands": "5.0.4", "@graphql-inspector/config": "4.0.2", - "@graphql-inspector/coverage-command": "5.0.3", - "@graphql-inspector/diff-command": "4.0.3", - "@graphql-inspector/docs-command": "4.0.3", - "@graphql-inspector/git-loader": "4.0.2", - "@graphql-inspector/github-loader": "4.0.2", - "@graphql-inspector/graphql-loader": "4.0.2", - "@graphql-inspector/introspect-command": "4.0.3", - "@graphql-inspector/json-loader": "4.0.2", - "@graphql-inspector/loaders": "4.0.3", - "@graphql-inspector/serve-command": "4.0.3", - "@graphql-inspector/similar-command": "4.0.3", - "@graphql-inspector/url-loader": "4.0.2", - "@graphql-inspector/validate-command": "4.0.3", + "@graphql-inspector/coverage-command": "6.1.0", + "@graphql-inspector/diff-command": "5.0.6", + "@graphql-inspector/docs-command": "5.0.4", + "@graphql-inspector/git-loader": "5.0.1", + "@graphql-inspector/github-loader": "5.0.1", + "@graphql-inspector/graphql-loader": "5.0.1", + "@graphql-inspector/introspect-command": "5.0.6", + "@graphql-inspector/json-loader": "5.0.1", + "@graphql-inspector/loaders": "4.0.5", + "@graphql-inspector/serve-command": "5.0.5", + "@graphql-inspector/similar-command": "5.0.6", + "@graphql-inspector/url-loader": "5.0.1", + "@graphql-inspector/validate-command": "5.0.6", "tslib": "2.6.2", "yargs": "17.7.2" }, @@ -2854,77 +2871,40 @@ "graphql-inspector": "cjs/index.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/code-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/code-loader/-/code-loader-4.0.2.tgz", - "integrity": "sha512-WNMorwDSknFFnv2GF3FvjuxQy1VMxIVMsTgL3E6amhzSPvKd3SPplw9rpqhQJX9jKXHvu70PgE2FvbPCTapE5A==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/code-loader/-/code-loader-5.0.1.tgz", + "integrity": "sha512-kdyP76g0QrtOFRda67+aNshSf0PXYyGJLiGxoVBogpAbkzDRhZQZAsdQVKP0tdEQAn4w0zN6VBdmpF/PAeBO5A==", "dependencies": { - "@graphql-tools/code-file-loader": "8.0.1", + "@graphql-tools/code-file-loader": "8.1.2", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/code-loader/node_modules/@graphql-tools/code-file-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.0.1.tgz", - "integrity": "sha512-pmg81lsIXGW3uW+nFSCIG0lFQIxWVbgDjeBkSWlnP8CZsrHTQEkB53DT7t4BHLryoxDS4G4cPxM52yNINDSL8w==", - "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.0.1", - "@graphql-tools/utils": "^10.0.0", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/code-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.0.1.tgz", - "integrity": "sha512-4sfBJSoXxVB4rRCCp2GTFhAYsUJgAPSKxSV+E3Voc600mK52JO+KsHCCTnPgCeyJFMNR9l94J6+tqxVKmlqKvw==", - "dependencies": { - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-inspector/commands": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/commands/-/commands-4.0.3.tgz", - "integrity": "sha512-68RO/nvt/9cKNmBpMCdExp1PkIeEdUpmv5WtcJwrIneTF/A9Cg45z2oDGiYr0CZgknprFgYQNVNFbYJV9AIamg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@graphql-inspector/commands/-/commands-5.0.4.tgz", + "integrity": "sha512-m6SzYxjkKhor7pV33r1FSL2Wq/epzeWDE1cfPT/eFJ4qKavTBcglr+Vpien6PK1a2vy69GhviFhMoJEakrlZMA==", "dependencies": { "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "@graphql-inspector/config": "4.0.2", - "@graphql-inspector/loaders": "4.0.3", + "@graphql-inspector/config": "^4.0.0", + "@graphql-inspector/loaders": "^4.0.0", "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", "yargs": "17.7.2" } @@ -2944,45 +2924,54 @@ } }, "node_modules/@graphql-inspector/core": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/core/-/core-5.0.2.tgz", - "integrity": "sha512-pXHPCggwLmgi5NACPPV4qyf2xW/sQONnu6ZqCAid3k/S2APmVYN4Z3OvxvLA12NFhzby5Sz5K4fRsId43cK8ww==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-inspector/core/-/core-6.1.0.tgz", + "integrity": "sha512-5/kqD5330duUsfMBfhMc0iVld76JwSKTkKi7aOr1x9MvSnP8p1anQo7BCNZ5VY9+EvWn4njHbkNfdS/lrqsi+A==", "dependencies": { - "dependency-graph": "0.11.0", - "object-inspect": "1.12.3", + "dependency-graph": "1.0.0", + "object-inspect": "1.13.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/@graphql-inspector/core/node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "engines": { + "node": ">=4" + } + }, "node_modules/@graphql-inspector/coverage-command": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/coverage-command/-/coverage-command-5.0.3.tgz", - "integrity": "sha512-LeAsn9+LjyxCzRnDvcfnQT6I0cI8UWnjPIxDkHNlkJLB0YWUTD1Z73fpRdw+l2kbYgeoMLFOK8TmilJjFN1+qQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-inspector/coverage-command/-/coverage-command-6.1.0.tgz", + "integrity": "sha512-c5/nERSACMkVkjlKF6ggUwI4nuKTyO991fqQiM9Dm36Heahu1BsH5BjtHWY6zlOg5b7e0v7X0+wqsLjNUsYGEA==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", - "@graphql-tools/utils": "10.0.3", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/coverage-command/node_modules/@graphql-tools/utils": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.3.tgz", - "integrity": "sha512-6uO41urAEIs4sXQT2+CYGsUTkHkVo/2MpM/QjoHj6D6xoEF2woXHBpdAVi0HKIInDwZqWgEYOwIFez0pERxa1Q==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", "dset": "^3.1.2", "tslib": "^2.4.0" }, @@ -2994,362 +2983,140 @@ } }, "node_modules/@graphql-inspector/diff-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/diff-command/-/diff-command-4.0.3.tgz", - "integrity": "sha512-9yDP4brhY44sgRMsy0hUJxoBLvem4J74XbQEU+87eecKDZ6SttiiadnQ6935SaQF/DOx0b6P1gH2phknDL/EDw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/diff-command/-/diff-command-5.0.6.tgz", + "integrity": "sha512-DmFfOM5QHQphBvPyyRNmei1IY9DvoqySd/D3iV7/1iOBnFxNkljM8jqbUGe+jZfcrDdM9DRYJseb5d0atSXBRQ==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/docs-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/docs-command/-/docs-command-4.0.3.tgz", - "integrity": "sha512-69DPm0JpQPUfm1myNV6WqTuT9toG1fEwsheYCJf2Wn/P36HQ+neYTWbsXJF7EwSPzUsT5JWVxMyGg3RfX7IkOw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@graphql-inspector/docs-command/-/docs-command-5.0.4.tgz", + "integrity": "sha512-NTQRWYzGNJy4Bnd+0NHNjOdgaETEUG112W+Nei/tPCRTs0Vi/UiW+UkGsQ3KxJozEkwgN8od39bVWohGTOPcpA==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", + "@graphql-inspector/commands": "5.0.4", "open": "8.4.2", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/git-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/git-loader/-/git-loader-4.0.2.tgz", - "integrity": "sha512-RLDj4xZWpYt3gNgtu4hiAOTpabmBwcePLSs7Lem558ma7+Ud1tjRhKhVfY2/wIFGNgD7pTzVHvyB0Yc7bD6Wpg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/git-loader/-/git-loader-5.0.1.tgz", + "integrity": "sha512-eZFNU/y1z4sZ9Axu8mB/J7mW+e78JnWgXG2vcT1TT2E1uzFm0x2oNONM2lgLCZGEJuwQDEnreok5CoHumIdE4Q==", "dependencies": { - "@graphql-tools/git-loader": "8.0.1", + "@graphql-tools/git-loader": "8.0.6", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/git-loader/node_modules/@graphql-tools/git-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.1.tgz", - "integrity": "sha512-ivNtxD+iEfpPONYKip0kbpZMRdMCNR3HrIui8NCURmUdvBYGaGcbB3VrGMhxwZuzc+ybhs2ralPt1F8Oxq2jLA==", - "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.0.1", - "@graphql-tools/utils": "^10.0.0", - "is-glob": "4.0.3", - "micromatch": "^4.0.4", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/git-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.0.1.tgz", - "integrity": "sha512-4sfBJSoXxVB4rRCCp2GTFhAYsUJgAPSKxSV+E3Voc600mK52JO+KsHCCTnPgCeyJFMNR9l94J6+tqxVKmlqKvw==", - "dependencies": { - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-inspector/github-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/github-loader/-/github-loader-4.0.2.tgz", - "integrity": "sha512-SrOUjz1RppK5Vxj8fMoCN+i+yvzIj1ZnP+mNdLZIsms9ou6u0+h8NuE/KfANR5Z1VJIQGIifMONIv2eTx4SjCQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/github-loader/-/github-loader-5.0.1.tgz", + "integrity": "sha512-CDsY4V1pEDzr5z5FlYTxcPa/7pKsuT/6xQmo1JghHQuYQPZ5TjtGsyNZwgQOjISMCw7pknXfifPBrFQKt6IOEA==", "dependencies": { - "@graphql-tools/github-loader": "8.0.0", + "@graphql-tools/github-loader": "8.0.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/github-loader/node_modules/@graphql-tools/github-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.0.tgz", - "integrity": "sha512-VuroArWKcG4yaOWzV0r19ElVIV6iH6UKDQn1MXemND0xu5TzrFme0kf3U9o0YwNo0kUYEk9CyFM0BYg4he17FA==", - "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/executor-http": "^1.0.0", - "@graphql-tools/graphql-tag-pluck": "^8.0.0", - "@graphql-tools/utils": "^10.0.0", - "@whatwg-node/fetch": "^0.9.0", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/github-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", - "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", - "dependencies": { - "@babel/core": "^7.22.9", - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.13", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/github-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@graphql-inspector/github-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", - "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@graphql-inspector/github-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", - "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@graphql-inspector/github-loader/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" - }, "node_modules/@graphql-inspector/graphql-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/graphql-loader/-/graphql-loader-4.0.2.tgz", - "integrity": "sha512-cYBLE5LNQbnK3jOmi49IaaEcBjfLIYyGOY89ZH0O4kgOmKJ5WgbQCLK5xL6vCl2IXkCQy52EmN8KlVr1gVOnmw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/graphql-loader/-/graphql-loader-5.0.1.tgz", + "integrity": "sha512-VZIcbkMhgak3sW4GehVIX/Qnwu1TmQidvaWs8YUiT+czPxKK1rqY/c/G3arwQDtqAdPMx8IwY1bT83ykfIyxfg==", "dependencies": { - "@graphql-tools/graphql-file-loader": "8.0.0", + "@graphql-tools/graphql-file-loader": "8.0.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/graphql-loader/node_modules/@graphql-tools/graphql-file-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.0.tgz", - "integrity": "sha512-wRXj9Z1IFL3+zJG1HWEY0S4TXal7+s1vVhbZva96MSp0kbb/3JBF7j0cnJ44Eq0ClccMgGCDFqPFXty4JlpaPg==", - "dependencies": { - "@graphql-tools/import": "7.0.0", - "@graphql-tools/utils": "^10.0.0", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/graphql-loader/node_modules/@graphql-tools/import": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.0.tgz", - "integrity": "sha512-NVZiTO8o1GZs6OXzNfjB+5CtQtqsZZpQOq+Uu0w57kdUkT4RlQKlwhT8T81arEsbV55KpzkpFsOZP7J1wdmhBw==", - "dependencies": { - "@graphql-tools/utils": "^10.0.0", - "resolve-from": "5.0.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-inspector/introspect-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/introspect-command/-/introspect-command-4.0.3.tgz", - "integrity": "sha512-43yJvNxGOTzoqZPGAeocC4qBSYAQ09f1QX8bzkinzWvGS8WViWGlYwN6Lxup41GOUNscl7EPp1UMl+IRU5Yu3Q==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/introspect-command/-/introspect-command-5.0.6.tgz", + "integrity": "sha512-qtYZLObzezNR5aS1qFeECwcK+MQGWDDywI3UzSKKtTLfoQJTAv3ECoo9PZxGFC2aEmZAKTEfkFICkbE9OCHu/w==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/json-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/json-loader/-/json-loader-4.0.2.tgz", - "integrity": "sha512-cbSs/5Mzw0ltm9/SkDHKpq2u1yqEwxAuMLBs7uG+lqaFGgkIuzN9U6i4LRRYgBJcfn/mScodlvo0W0IG83Wn3g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/json-loader/-/json-loader-5.0.1.tgz", + "integrity": "sha512-ql5zI2E/RNgLKDJ2HilTds2lUTv8ZXQfY5HG29iia85q/CIFslVTDbhzhbXRqmz4jsLd3KCi1LxpAeYQQMhCSQ==", "dependencies": { - "@graphql-tools/json-file-loader": "8.0.0", + "@graphql-tools/json-file-loader": "8.0.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/json-loader/node_modules/@graphql-tools/json-file-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.0.tgz", - "integrity": "sha512-ki6EF/mobBWJjAAC84xNrFMhNfnUFD6Y0rQMGXekrUgY0NdeYXHU0ZUgHzC9O5+55FslqUmAUHABePDHTyZsLg==", - "dependencies": { - "@graphql-tools/utils": "^10.0.0", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-inspector/loaders": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/loaders/-/loaders-4.0.3.tgz", - "integrity": "sha512-V9T+IUJgmneWx1kEaSbFHLcjMs929TKLs6m3p544/CMPdFIsRYmYhd2jfUBhzXC8P6avnsLxxzVEOR45wJgOYA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@graphql-inspector/loaders/-/loaders-4.0.5.tgz", + "integrity": "sha512-MQj82Pbo4YVgS1E3IjVvP3ByLQKQ6HHrjK+S21szXx46cKPxlc+MeKHpjfERSCmbdKAinP0MMHxVrmk7hyktow==", "dependencies": { - "@graphql-tools/code-file-loader": "8.0.1", - "@graphql-tools/load": "8.0.0", - "@graphql-tools/utils": "10.0.3", + "@graphql-tools/code-file-loader": "8.1.2", + "@graphql-tools/load": "8.0.2", + "@graphql-tools/utils": "10.2.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "@graphql-inspector/config": "4.0.2", + "@graphql-inspector/config": "^4.0.0", "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/loaders/node_modules/@graphql-tools/code-file-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.0.1.tgz", - "integrity": "sha512-pmg81lsIXGW3uW+nFSCIG0lFQIxWVbgDjeBkSWlnP8CZsrHTQEkB53DT7t4BHLryoxDS4G4cPxM52yNINDSL8w==", - "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.0.1", - "@graphql-tools/utils": "^10.0.0", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/loaders/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.0.1.tgz", - "integrity": "sha512-4sfBJSoXxVB4rRCCp2GTFhAYsUJgAPSKxSV+E3Voc600mK52JO+KsHCCTnPgCeyJFMNR9l94J6+tqxVKmlqKvw==", - "dependencies": { - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/loaders/node_modules/@graphql-tools/load": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.0.tgz", - "integrity": "sha512-Cy874bQJH0FP2Az7ELPM49iDzOljQmK1PPH6IuxsWzLSTxwTqd8dXA09dcVZrI7/LsN26heTY2R8q2aiiv0GxQ==", - "dependencies": { - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.0.0", - "p-limit": "3.1.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-inspector/loaders/node_modules/@graphql-tools/utils": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.3.tgz", - "integrity": "sha512-6uO41urAEIs4sXQT2+CYGsUTkHkVo/2MpM/QjoHj6D6xoEF2woXHBpdAVi0HKIInDwZqWgEYOwIFez0pERxa1Q==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", "dset": "^3.1.2", "tslib": "^2.4.0" }, @@ -3361,160 +3128,95 @@ } }, "node_modules/@graphql-inspector/logger": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/logger/-/logger-4.0.2.tgz", - "integrity": "sha512-2VLU90HjQyhwFgiU5uBs1+sBjs71Q42MWBQnkc3GSAaVCE+bzYRurO4bRS/z7UXl0hJsJda41z5QPOFeyeMMwQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/logger/-/logger-5.0.1.tgz", + "integrity": "sha512-rEo+HoQt+qjdayy7p5vcR9GeGTdKXmN0LbIm3W+jKKoXeAMlV4zHxnOW6jEhO6E0eVQxf8Sc1TlcH78i2P2a9w==", "dependencies": { "chalk": "4.1.2", "figures": "3.2.0", "log-symbols": "4.1.0", - "std-env": "3.3.3", + "std-env": "3.7.0", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-inspector/serve-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/serve-command/-/serve-command-4.0.3.tgz", - "integrity": "sha512-+8vovcRBFjrzZ+E5QTsG8GXSHm5q2czuqHosAU8bx6tfv2NM3FoSHUkctZulubA0+rodt2xrsD1L3R0ZCR8TBQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@graphql-inspector/serve-command/-/serve-command-5.0.5.tgz", + "integrity": "sha512-dWAg51LHrXoZb5NqE/G/nTaeJ2FrQJZc+mCz6l3fTBL6pU6szyMx4+Cxq+JmGCJVD71N4Fh+h9B4psDpnOFtBQ==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/logger": "4.0.2", - "graphql-yoga": "4.0.3", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/logger": "5.0.1", + "graphql-yoga": "5.3.1", "open": "8.4.2", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-inspector/similar-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/similar-command/-/similar-command-4.0.3.tgz", - "integrity": "sha512-qWJ+E0UFzba05ymwxu/M9Yb3J4GBj97T/NNEAaflgFaVjsiYuZEywfYekKMt1kALQOP8pufH0yDOYBHObKO9sQ==", - "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-inspector/url-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/url-loader/-/url-loader-4.0.2.tgz", - "integrity": "sha512-bor9yZ6PIW8Fc5swo8t5TeexiQS8nRySF3oF4iG8d/aYPh0e7/DYM3lGF4M7VY3lH760r3caIRXXNOnn79tBrw==", - "dependencies": { - "@graphql-tools/url-loader": "8.0.0", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/url-loader/node_modules/@graphql-tools/url-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.0.tgz", - "integrity": "sha512-rPc9oDzMnycvz+X+wrN3PLrhMBQkG4+sd8EzaFN6dypcssiefgWKToXtRKI8HHK68n2xEq1PyrOpkjHFJB+GwA==", + "node_modules/@graphql-inspector/similar-command": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/similar-command/-/similar-command-5.0.6.tgz", + "integrity": "sha512-40SaZtxIXEZ0V/EkiG6N2in+PSeVoVcwmtl1ETbysGXZ6xC9Fu+Qi0lE7lbKWKjzw5nv9hYpznDJ1oIsBuN+hQ==", "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/delegate": "^10.0.0", - "@graphql-tools/executor-graphql-ws": "^1.0.0", - "@graphql-tools/executor-http": "^1.0.0", - "@graphql-tools/executor-legacy-ws": "^1.0.0", - "@graphql-tools/utils": "^10.0.0", - "@graphql-tools/wrap": "^10.0.0", - "@types/ws": "^8.0.0", - "@whatwg-node/fetch": "^0.9.0", - "isomorphic-ws": "^5.0.0", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.11", - "ws": "^8.12.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-inspector/url-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "engines": { - "node": ">=16.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/url-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "node_modules/@graphql-inspector/url-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/url-loader/-/url-loader-5.0.1.tgz", + "integrity": "sha512-7OPJfTJgqptJyfsrpntsn3GEMpSZWxkJO+KaMIZfqDsiWN/zyvNqB0Amogi3d7xxtU1fnB3NCN5VWCFuiRSPXg==", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" + "@graphql-tools/url-loader": "8.0.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@graphql-inspector/url-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", - "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "node": ">=18.0.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/url-loader/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" - }, "node_modules/@graphql-inspector/validate-command": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@graphql-inspector/validate-command/-/validate-command-4.0.3.tgz", - "integrity": "sha512-wOcnkpU9zA30y1ggnJH1IvRDlEM24LP1J8/3tQx8rVn4zfBQpIVb7s31F/N++mZBAWXPTp2+t0JiUNbHR82odA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/validate-command/-/validate-command-5.0.6.tgz", + "integrity": "sha512-SO4esOmsdUEueGA2kMjsoXSrvQrtZnJF7wKhcUwJrxIBm8aHf1V5wtorAk4ajIzhlD6a5Yd35GHI9hbjXnIZuQ==", "dependencies": { - "@graphql-inspector/commands": "4.0.3", - "@graphql-inspector/core": "5.0.2", - "@graphql-inspector/logger": "4.0.2", - "@graphql-tools/utils": "10.0.3", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.1.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@graphql-inspector/validate-command/node_modules/@graphql-tools/utils": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.3.tgz", - "integrity": "sha512-6uO41urAEIs4sXQT2+CYGsUTkHkVo/2MpM/QjoHj6D6xoEF2woXHBpdAVi0HKIInDwZqWgEYOwIFez0pERxa1Q==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", "dset": "^3.1.2", "tslib": "^2.4.0" }, @@ -3605,12 +3307,11 @@ } }, "node_modules/@graphql-tools/code-file-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.1.tgz", - "integrity": "sha512-q4KN25EPSUztc8rA8YUU3ufh721Yk12xXDbtUA+YstczWS7a1RJlghYMFEfR1HsHSYbF7cUqkbnTKSGM3o52bQ==", - "dev": true, + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.2.tgz", + "integrity": "sha512-GrLzwl1QV2PT4X4TEEfuTmZYzIZHLqoTGBjczdUzSqgCCcqwWzLB3qrJxFQfI8e5s1qZ1bhpsO9NoMn7tvpmyA==", "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.0", + "@graphql-tools/graphql-tag-pluck": "8.3.1", "@graphql-tools/utils": "^10.0.13", "globby": "^11.0.3", "tslib": "^2.4.0", @@ -3624,10 +3325,9 @@ } }, "node_modules/@graphql-tools/code-file-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", - "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", - "dev": true, + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", + "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", "dependencies": { "@babel/core": "^7.22.9", "@babel/parser": "^7.16.8", @@ -3680,9 +3380,9 @@ } }, "node_modules/@graphql-tools/executor": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.4.tgz", - "integrity": "sha512-aCO/5LEAwyTWObAAfpLlwAjaOjTxRX6YNXcGW62mglQhPBy+j0fTc4desci/4nJ49l8FWETaTG0MZ1G/PqQslg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.6.tgz", + "integrity": "sha512-+1kjfqzM5T2R+dCw7F4vdJ3CqG+fY/LYJyhNiWEFtq0ToLwYzR/KKyD8YuzTirEjSxWTVlcBh7endkx5n5F6ew==", "dependencies": { "@graphql-tools/utils": "^10.1.1", "@graphql-typed-document-node/core": "3.2.0", @@ -3795,12 +3495,11 @@ } }, "node_modules/@graphql-tools/git-loader": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.5.tgz", - "integrity": "sha512-P97/1mhruDiA6D5WUmx3n/aeGPLWj2+4dpzDOxFGGU+z9NcI/JdygMkeFpGZNHeJfw+kHfxgPcMPnxHcyhAoVA==", - "dev": true, + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.6.tgz", + "integrity": "sha512-FQFO4H5wHAmHVyuUQrjvPE8re3qJXt50TWHuzrK3dEaief7JosmlnkLMDMbMBwtwITz9u1Wpl6doPhT2GwKtlw==", "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.0", + "@graphql-tools/graphql-tag-pluck": "8.3.1", "@graphql-tools/utils": "^10.0.13", "is-glob": "4.0.3", "micromatch": "^4.0.4", @@ -3815,10 +3514,9 @@ } }, "node_modules/@graphql-tools/git-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", - "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", - "dev": true, + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", + "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", "dependencies": { "@babel/core": "^7.22.9", "@babel/parser": "^7.16.8", @@ -3839,7 +3537,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.1.tgz", "integrity": "sha512-W4dFLQJ5GtKGltvh/u1apWRFKBQOsDzFxO9cJkOYZj1VzHCpRF43uLST4VbCfWve+AwBqOuKr7YgkHoxpRMkcg==", - "dev": true, "dependencies": { "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/executor-http": "^1.0.9", @@ -3860,7 +3557,6 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", - "dev": true, "dependencies": { "@babel/core": "^7.22.9", "@babel/parser": "^7.16.8", @@ -3881,7 +3577,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, "engines": { "node": ">=16.0.0" } @@ -3890,7 +3585,6 @@ "version": "0.9.17", "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", - "dev": true, "dependencies": { "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" @@ -3903,7 +3597,6 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", - "dev": true, "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -3918,14 +3611,12 @@ "node_modules/@graphql-tools/github-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, "node_modules/@graphql-tools/graphql-file-loader": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.1.tgz", "integrity": "sha512-7gswMqWBabTSmqbaNyWSmRRpStWlcCkBc73E6NZNlh4YNuiyKOwbvSkOUYFOqFMfEL+cFsXgAvr87Vz4XrYSbA==", - "dev": true, "dependencies": { "@graphql-tools/import": "7.0.1", "@graphql-tools/utils": "^10.0.13", @@ -3974,7 +3665,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.1.tgz", "integrity": "sha512-935uAjAS8UAeXThqHfYVr4HEAp6nHJ2sximZKO1RzUTq5WoALMAhhGARl0+ecm6X+cqNUwIChJbjtaa6P/ML0w==", - "dev": true, "dependencies": { "@graphql-tools/utils": "^10.0.13", "resolve-from": "5.0.0", @@ -3991,7 +3681,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.1.tgz", "integrity": "sha512-lAy2VqxDAHjVyqeJonCP6TUemrpYdDuKt25a10X6zY2Yn3iFYGnuIDQ64cv3ytyGY6KPyPB+Kp+ZfOkNDG3FQA==", - "dev": true, "dependencies": { "@graphql-tools/utils": "^10.0.13", "globby": "^11.0.3", @@ -4009,7 +3698,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.2.tgz", "integrity": "sha512-S+E/cmyVmJ3CuCNfDuNF2EyovTwdWfQScXv/2gmvJOti2rGD8jTt9GYVzXaxhblLivQR9sBUCNZu/w7j7aXUCA==", - "dev": true, "dependencies": { "@graphql-tools/schema": "^10.0.3", "@graphql-tools/utils": "^10.0.13", @@ -4164,12 +3852,12 @@ } }, "node_modules/@graphql-tools/schema": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.3.tgz", - "integrity": "sha512-p28Oh9EcOna6i0yLaCFOnkcBDQECVf3SCexT6ktb86QNj9idnkhI+tCxnwZDh58Qvjd2nURdkbevvoZkvxzCog==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.4.tgz", + "integrity": "sha512-HuIwqbKxPaJujox25Ra4qwz0uQzlpsaBOzO6CVfzB/MemZdd+Gib8AIvfhQArK0YIN40aDran/yi+E5Xf0mQww==", "dependencies": { "@graphql-tools/merge": "^9.0.3", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.2.1", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, @@ -4184,7 +3872,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.2.tgz", "integrity": "sha512-1dKp2K8UuFn7DFo1qX5c1cyazQv2h2ICwA9esHblEqCYrgf69Nk8N7SODmsfWg94OEaI74IqMoM12t7eIGwFzQ==", - "dev": true, "dependencies": { "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/delegate": "^10.0.4", @@ -4211,7 +3898,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, "engines": { "node": ">=16.0.0" } @@ -4220,7 +3906,6 @@ "version": "0.9.17", "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", - "dev": true, "dependencies": { "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" @@ -4233,7 +3918,6 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", - "dev": true, "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -4248,13 +3932,12 @@ "node_modules/@graphql-tools/url-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, "node_modules/@graphql-tools/utils": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.0.tgz", - "integrity": "sha512-HYV7dO6pNA2nGKawygaBpk8y+vXOUjjzzO43W/Kb7EPRmXUEQKjHxPYRvQbiF72u1N3XxwGK5jnnFk9WVhUwYw==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.2.tgz", + "integrity": "sha512-iaqOHS4f90KNADBHqVsRBjKpM6iSvsUg1q5GhWMK03loYLaDzftrEwcsl0OkSSnRhJvAsT7q4q3r3YzRoV0v1g==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "cross-inspect": "1.0.0", @@ -4295,28 +3978,28 @@ } }, "node_modules/@graphql-yoga/logger": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-1.0.0.tgz", - "integrity": "sha512-JYoxwnPggH2BfO+dWlWZkDeFhyFZqaTRGLvFhy+Pjp2UxitEW6nDrw+pEDw/K9tJwMjIFMmTT9VfTqrnESmBHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-2.0.0.tgz", + "integrity": "sha512-Mg8psdkAp+YTG1OGmvU+xa6xpsAmSir0hhr3yFYPyLNwzUj95DdIwsMpKadDj9xDpYgJcH3Hp/4JMal9DhQimA==", "dependencies": { "tslib": "^2.5.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-yoga/subscription": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-4.0.0.tgz", - "integrity": "sha512-0qsN/BPPZNMoC2CZ8i+P6PgiJyHh1H35aKDt37qARBDaIOKDQuvEOq7+4txUKElcmXi7DYFo109FkhSQoEajrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.0.tgz", + "integrity": "sha512-Ri7sK8hmxd/kwaEa0YT8uqQUb2wOLsmBMxI90QDyf96lzOMJRgBuNYoEkU1pSgsgmW2glceZ96sRYfaXqwVxUw==", "dependencies": { - "@graphql-yoga/typed-event-target": "^2.0.0", + "@graphql-yoga/typed-event-target": "^3.0.0", "@repeaterjs/repeater": "^3.0.4", "@whatwg-node/events": "^0.1.0", "tslib": "^2.5.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-yoga/subscription/node_modules/@whatwg-node/events": { @@ -4328,15 +4011,15 @@ } }, "node_modules/@graphql-yoga/typed-event-target": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-2.0.0.tgz", - "integrity": "sha512-oA/VGxGmaSDym1glOHrltw43qZsFwLLjBwvh57B79UKX/vo3+UQcRgOyE44c5RP7DCYjkrC2tuArZmb6jCzysw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.0.tgz", + "integrity": "sha512-w+liuBySifrstuHbFrHoHAEyVnDFVib+073q8AeAJ/qqJfvFvAwUPLLtNohR/WDVRgSasfXtl3dcNuVJWN+rjg==", "dependencies": { "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.5.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@humanwhocodes/config-array": { @@ -4609,36 +4292,45 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", - "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", + "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", "dev": true }, "node_modules/@microsoft/tsdoc-config": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", - "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", + "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "ajv": "~6.12.6", + "@microsoft/tsdoc": "0.15.0", + "ajv": "~8.12.0", "jju": "~1.4.0", - "resolve": "~1.19.0" + "resolve": "~1.22.2" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", @@ -4974,82 +4666,6 @@ "node": ">= 8" } }, - "node_modules/@opencensus/core": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.9.tgz", - "integrity": "sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==", - "dependencies": { - "continuation-local-storage": "^3.2.1", - "log-driver": "^1.2.7", - "semver": "^5.5.0", - "shimmer": "^1.2.0", - "uuid": "^3.2.1" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@opencensus/core/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@opencensus/core/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/@opencensus/propagation-b3": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@opencensus/propagation-b3/-/propagation-b3-0.0.8.tgz", - "integrity": "sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==", - "dependencies": { - "@opencensus/core": "^0.0.8", - "uuid": "^3.2.1" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@opencensus/propagation-b3/node_modules/@opencensus/core": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.8.tgz", - "integrity": "sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ==", - "dependencies": { - "continuation-local-storage": "^3.2.1", - "log-driver": "^1.2.7", - "semver": "^5.5.0", - "shimmer": "^1.2.0", - "uuid": "^3.2.1" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@opencensus/propagation-b3/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@opencensus/propagation-b3/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/@parcel/watcher": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", @@ -5455,12 +5071,10 @@ } }, "node_modules/@pm2/io": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-5.0.2.tgz", - "integrity": "sha512-XAvrNoQPKOyO/jJyCu8jPhLzlyp35MEf7w/carHXmWKddPzeNOFSEpSEqMzPDawsvpxbE+i918cNN+MwgVsStA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.0.0.tgz", + "integrity": "sha512-sKUEgZoQ5/jRwTyMB1I7u2wXL6dG0j/F/M4ANJ7dJCApfW8nWC0RElMW2siEKvZ79iplIPAaWV27oyBoerEflw==", "dependencies": { - "@opencensus/core": "0.0.9", - "@opencensus/propagation-b3": "0.0.8", "async": "~2.6.1", "debug": "~4.3.1", "eventemitter2": "^6.3.1", @@ -5650,9 +5264,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.16", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.16.tgz", - "integrity": "sha512-X1a3xQ5kEMvTib5fBrHKh6Y+pXbeKXqziYuxOUo1ojQNECg4M5Etd1qqyhMap+lFUOAh8S7UYevgJHOm4A+NOg==", + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.17.tgz", + "integrity": "sha512-IPvU9A31qRCZ7lds/x+ksuK/UMndd0EASveAvCvEtFFKIZjZ+m/a4a0L7S28KEWoR5ka8526hlSghDo4Hrc2Hg==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -5907,6 +5521,15 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", + "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.4" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -6073,6 +5696,15 @@ "graphql": "^16.3.0" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "peer": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-assert": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", @@ -6155,9 +5787,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", "dev": true }, "node_modules/@types/long": { @@ -6190,9 +5822,9 @@ } }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", "dependencies": { "undici-types": "~5.26.4" } @@ -6296,6 +5928,12 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "peer": true + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -6303,9 +5941,9 @@ "dev": true }, "node_modules/@types/validator": { - "version": "13.11.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz", - "integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==", "dev": true }, "node_modules/@types/webidl-conversions": { @@ -6343,16 +5981,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz", - "integrity": "sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/type-utils": "7.10.0", - "@typescript-eslint/utils": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6376,15 +6014,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz", - "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", + "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", "debug": "^4.3.4" }, "engines": { @@ -6403,14 +6041,101 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", + "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", + "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", + "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", + "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", - "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0" + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6421,13 +6146,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz", - "integrity": "sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.10.0", - "@typescript-eslint/utils": "7.10.0", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -6448,9 +6173,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", - "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6461,13 +6186,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", - "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6501,15 +6226,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0" + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6523,12 +6248,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", - "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/types": "7.14.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -6572,12 +6297,6 @@ "vitest": "1.6.0" } }, - "node_modules/@vitest/coverage-v8/node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true - }, "node_modules/@vitest/expect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", @@ -6707,9 +6426,9 @@ } }, "node_modules/@whatwg-node/server": { - "version": "0.9.31", - "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.31.tgz", - "integrity": "sha512-crd8CPnKX6UELsnoU8G4DjAjMYVgNykHsZiRZMaglwqICxY4s2J2QggU0Ism00wbtwJ43umEOGYnwE389BHGOg==", + "version": "0.9.34", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.34.tgz", + "integrity": "sha512-1sHRjqUtZIyTR2m2dS/dJpzS5OcNDpPuUSVDa2PoEgzYVKr4GsqJaYtRaEXXFohvvyh6PkouYCc1rE7jMDWVCA==", "dependencies": { "@whatwg-node/fetch": "^0.9.17", "tslib": "^2.3.1" @@ -6727,9 +6446,9 @@ } }, "node_modules/@whatwg-node/server/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.18.tgz", + "integrity": "sha512-hqoz6StCW+AjV/3N+vg0s1ah82ptdVUb9nH2ttj3UbySOXUvytWw2yqy8c1cKzyRk6mDD00G47qS3fZI9/gMjg==", "dependencies": { "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" @@ -6739,9 +6458,9 @@ } }, "node_modules/@whatwg-node/server/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", + "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -6894,12 +6613,6 @@ "node": ">=8" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "peer": true - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6937,8 +6650,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", @@ -7156,26 +6868,6 @@ "node": "^4.7 || >=6.9 || >=7.3" } }, - "node_modules/async-listener": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", - "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", - "dependencies": { - "semver": "^5.3.0", - "shimmer": "^1.1.0" - }, - "engines": { - "node": "<=0.11.8 || >0.11.10" - } - }, - "node_modules/async-listener/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -7497,20 +7189,20 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/braces/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7559,9 +7251,9 @@ } }, "node_modules/bson": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", - "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", + "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", "engines": { "node": ">=16.20.1" } @@ -8103,12 +7795,12 @@ } }, "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/common-tags": { @@ -8140,7 +7832,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -8260,15 +7953,6 @@ "node": ">= 0.6" } }, - "node_modules/continuation-local-storage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", - "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", - "dependencies": { - "async-listener": "^0.6.0", - "emitter-listener": "^1.1.1" - } - }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -8641,6 +8325,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, "engines": { "node": ">= 0.6.0" } @@ -8901,15 +8586,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-abstract/node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -8982,9 +8658,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -8994,29 +8670,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -9260,13 +8936,13 @@ } }, "node_modules/eslint-plugin-tsdoc": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.17.tgz", - "integrity": "sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.3.0.tgz", + "integrity": "sha512-0MuFdBrrJVBjT/gyhkP2BqpD0np1NxNLfQ38xXDlSs/KVVpKI2A6vN7jx2Rve/CyUsvOsMGwp9KKrinv7q9g3A==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "@microsoft/tsdoc-config": "0.16.2" + "@microsoft/tsdoc": "0.15.0", + "@microsoft/tsdoc-config": "0.17.0" } }, "node_modules/eslint-scope": { @@ -9527,9 +9203,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz", - "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.3.1.tgz", + "integrity": "sha512-BbaryvkY4wEgDqLgD18/NSy2lDO2jTuT9Y8c1Mpx0X63Yz0sYd5zN6KPe7UvpuSVvV33T6RaE1o1IVZQjHMYgw==", "engines": { "node": ">= 16" }, @@ -9931,9 +9607,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -10007,7 +9683,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -10248,9 +9925,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", - "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -10295,6 +9972,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -10326,6 +10004,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10335,6 +10014,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10407,9 +10087,10 @@ "dev": true }, "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -10621,24 +10302,24 @@ } }, "node_modules/graphql-yoga": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-4.0.3.tgz", - "integrity": "sha512-MP+v+yxCqM3lXg95vaA+kXjyRvyRxHUlgZryTecN7ugzEEnyQKKu8JBXA4ziEuLi3liRriyjCAyV4pqFzhHujA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.3.1.tgz", + "integrity": "sha512-n918QV6TF7xTjb9ASnozgsr4ydMc08c+x4eRAWKxxWVwSnzdP2xeN2zw1ljIzRD0ccSCNoBajGDKwcZkJDitPA==", "dependencies": { - "@envelop/core": "^4.0.0", - "@graphql-tools/executor": "^1.0.0", + "@envelop/core": "^5.0.0", + "@graphql-tools/executor": "^1.2.5", "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.0.0", - "@graphql-yoga/logger": "^1.0.0", - "@graphql-yoga/subscription": "^4.0.0", - "@whatwg-node/fetch": "^0.9.7", - "@whatwg-node/server": "^0.9.1", + "@graphql-tools/utils": "^10.1.0", + "@graphql-yoga/logger": "^2.0.0", + "@graphql-yoga/subscription": "^5.0.0", + "@whatwg-node/fetch": "^0.9.17", + "@whatwg-node/server": "^0.9.33", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0" @@ -10653,9 +10334,9 @@ } }, "node_modules/graphql-yoga/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.18.tgz", + "integrity": "sha512-hqoz6StCW+AjV/3N+vg0s1ah82ptdVUb9nH2ttj3UbySOXUvytWw2yqy8c1cKzyRk6mDD00G47qS3fZI9/gMjg==", "dependencies": { "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" @@ -10665,9 +10346,9 @@ } }, "node_modules/graphql-yoga/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", + "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -10680,9 +10361,9 @@ } }, "node_modules/graphql-yoga/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "engines": { "node": "14 || >=16.14" } @@ -10940,12 +10621,12 @@ } }, "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.1.tgz", + "integrity": "sha512-fCqlqLXcBnXa/TJXmT93/A36tJsjdJkibQ1MuIiFyCCYUlpYpIaj2mv1w+3KR6Rzu1IC3slFTje5f6DUp2A2rg==", "dev": true, "bin": { - "husky": "bin.mjs" + "husky": "bin.js" }, "engines": { "node": ">=18" @@ -11089,6 +10770,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -11739,15 +11421,15 @@ "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11805,7 +11487,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -11906,7 +11587,8 @@ "node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true }, "node_modules/jsonfile": { "version": "6.1.0", @@ -12081,12 +11763,15 @@ } }, "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -12094,22 +11779,31 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "peer": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/lint-staged": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", - "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.0.0", - "listr2": "8.0.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.4" + "version": "15.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz", + "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.4", + "execa": "~8.0.1", + "lilconfig": "~3.1.1", + "listr2": "~8.2.1", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.4.2" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -12219,16 +11913,16 @@ } }, "node_modules/lint-staged/node_modules/listr2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", - "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", + "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.0.0", - "rfdc": "^1.3.0", + "rfdc": "^1.3.1", "wrap-ansi": "^9.0.0" }, "engines": { @@ -12396,15 +12090,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/list-item": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", @@ -12583,14 +12268,6 @@ "lodash._reinterpolate": "^3.0.0" } }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "engines": { - "node": ">=0.8.6" - } - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -12813,6 +12490,41 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "peer": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "peer": true + }, "node_modules/markdown-link": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", @@ -12846,18 +12558,6 @@ "node": ">=0.10.0" } }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/math-interval-parser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz", @@ -12933,11 +12633,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12987,9 +12687,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -13009,9 +12709,9 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -13069,12 +12769,12 @@ "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" }, "node_modules/mongodb": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", - "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", + "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.4.0", + "bson": "^6.7.0", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -13144,13 +12844,58 @@ } }, "node_modules/mongoose-paginate-v2": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/mongoose-paginate-v2/-/mongoose-paginate-v2-1.8.1.tgz", - "integrity": "sha512-6CbkBnqnpnAPH/HSOsTL/70hg0N1/zWXB4DXlSA0Wz+wLd/tjDvI7vfqbaQqLt2yfqgv2Eb1Lf7cY33Ws2HqBw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mongoose-paginate-v2/-/mongoose-paginate-v2-1.8.2.tgz", + "integrity": "sha512-T/Z3qKyKnPUa6UkH1IjHxdYnYApCAKk9zb2C0GF5hg3QETcI62AUAUQGCBE2tIw7fF4feUaDARMajj/bersyvg==", "engines": { "node": ">=4.0.0" } }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13374,9 +13119,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "6.9.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", - "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", "engines": { "node": ">=6.0.0" } @@ -13456,9 +13201,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13589,6 +13334,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -13764,6 +13510,12 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -13862,6 +13614,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -13901,28 +13654,28 @@ } }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", "dev": true, "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/path-to-regexp": { @@ -14021,12 +13774,12 @@ } }, "node_modules/pm2": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.3.1.tgz", - "integrity": "sha512-DLVQHpSR1EegaTaRH3KbRXxpPVaqYwAp3uHSCtCsS++LSErvk07WSxuUnntFblBRqNU/w2KQyqs12mSq5wurkg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.4.0.tgz", + "integrity": "sha512-9TrDuckcSEnINoURygr3yfruK20qXuOUPOPQyIh4FIskQduyDNst9ys1XAt9YPY3RyGxVr2+x8Irsdma3klVQw==", "dependencies": { "@pm2/agent": "~2.0.0", - "@pm2/io": "~5.0.0", + "@pm2/io": "~6.0.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "latest", "async": "~3.2.0", @@ -14041,6 +13794,7 @@ "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", + "js-yaml": "~4.1.0", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "~3.0", @@ -14052,8 +13806,7 @@ "semver": "^7.2", "source-map-support": "0.5.21", "sprintf-js": "1.1.2", - "vizion": "~2.2.1", - "yamljs": "0.3.0" + "vizion": "~2.2.1" }, "bin": { "pm2": "bin/pm2", @@ -14062,7 +13815,7 @@ "pm2-runtime": "bin/pm2-runtime" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" }, "optionalDependencies": { "pm2-sysmonit": "^1.2.8" @@ -14376,6 +14129,15 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", @@ -14598,12 +14360,12 @@ } }, "node_modules/redis": { - "version": "4.6.14", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.14.tgz", - "integrity": "sha512-GrNg/e33HtsQwNXL7kJT+iNFPSwE1IPmd7wzV3j4f2z0EYxZfZE7FVTmUysgAtqQQtg5NXF5SNLR9OdO/UHOfw==", + "version": "4.6.15", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.15.tgz", + "integrity": "sha512-2NtuOpMW3tnYzBw6S8mbXSX7RPzvVFCA2wFJq9oErushO2UeBkxObk+uvo7gv7n0rhWeOj/IzrHO8TjcFlRSOg==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.16", + "@redis/client": "1.5.17", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", @@ -14796,6 +14558,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-in-the-middle": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", @@ -14915,40 +14686,57 @@ "dev": true }, "node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -15322,15 +15110,13 @@ } }, "node_modules/shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.3.tgz", + "integrity": "sha512-eneCLncGuvPdTutJuLyUGS8QNPAVFO5Trvld2wgEq1e002mwctAhJKeMGWtWVXOIEzmlcLRqcgPSorR6AVzOmQ==", "peer": true, "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" + "@shikijs/core": "1.10.3", + "@types/hast": "^3.0.4" } }, "node_modules/shimmer": { @@ -15355,14 +15141,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel/node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/sift": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", @@ -15596,9 +15374,9 @@ } }, "node_modules/std-env": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", - "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==" + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" }, "node_modules/streamsearch": { "version": "1.1.0", @@ -16162,13 +15940,13 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.3.tgz", - "integrity": "sha512-+fQnMqIp/jxZEXLcj6WzYy9FhcS5/Dfk8y4AtzJ6ejKcKqmfTF8Gso/jtrzDggCF2zTU20gJa6n8XqPYwDAUYQ==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz", + "integrity": "sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==", "dev": true, "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" + "esbuild": "~0.21.5", + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" @@ -16337,32 +16115,36 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typedoc": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz", - "integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==", + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.3.tgz", + "integrity": "sha512-6d2Sw9disvvpdk4K7VNjKr5/3hzijtfQVHRthhDqJgnhMHy1wQz4yPMJVKXElvnZhFr0nkzo+GzjXDTRV5yLpg==", "peer": true, "dependencies": { "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.7" + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.9.1", + "yaml": "^2.4.5" }, "bin": { "typedoc": "bin/typedoc" }, "engines": { - "node": ">= 16" + "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.0.1.tgz", - "integrity": "sha512-AfuIILU7aQbLMFyhX/qPT1yLrsQE9TiIxtmDKHRoQosDuHRS9CqA3aMOJyqmfE4Gn43vEQGb7vxEDwlrl3Sw8A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.1.tgz", + "integrity": "sha512-7hQt/1WaW/VI4+x3sxwcCGsEylP1E1GvF6OTTELK5sfTEp6AeK+83jkCOgZGp1pI2DiOammMYQMnxxOny9TKsQ==", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "typedoc": "0.25.x" + "typedoc": "0.26.x" } }, "node_modules/typescript": { @@ -16400,6 +16182,12 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "peer": true + }, "node_modules/ufo": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", @@ -16556,9 +16344,9 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -17157,12 +16945,6 @@ } } }, - "node_modules/vitest/node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true - }, "node_modules/vizion": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", @@ -17185,18 +16967,6 @@ "lodash": "^4.17.14" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "peer": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "peer": true - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -17441,12 +17211,13 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -17485,10 +17256,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", - "dev": true, + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", "bin": { "yaml": "bin.mjs" }, @@ -17502,32 +17272,6 @@ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "dev": true }, - "node_modules/yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", - "dependencies": { - "argparse": "^1.0.7", - "glob": "^7.0.5" - }, - "bin": { - "json2yaml": "bin/json2yaml", - "yaml2json": "bin/yaml2json" - } - }, - "node_modules/yamljs/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/yamljs/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 8f56fcc4ad..f634b418d5 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,10 @@ "dependencies": { "@apollo/server": "^4.10.4", "@faker-js/faker": "^8.2.0", - "@graphql-inspector/cli": "^4.0.3", + "@graphql-inspector/cli": "^5.0.6", "@graphql-tools/resolvers-composition": "^7.0.1", - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.2.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.3.2", "@parcel/watcher": "^2.4.1", "@types/graphql-upload": "^16.0.5", "@types/yargs": "^17.0.32", @@ -63,8 +63,8 @@ "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.2.0", - "graphql": "^16.8.1", + "express-rate-limit": "^7.3.1", + "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", "graphql-subscriptions": "^2.0.0", @@ -80,28 +80,28 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "markdown-toc": "^1.2.0", - "mongodb": "^6.3.0", + "mongodb": "^6.7.0", "mongoose": "^8.3.2", - "mongoose-paginate-v2": "^1.8.1", + "mongoose-paginate-v2": "^1.8.2", "morgan": "^1.10.0", "nanoid": "^5.0.7", - "nodemailer": "^6.9.12", - "pm2": "^5.2.0", - "redis": "^4.6.14", + "nodemailer": "^6.9.14", + "pm2": "^5.4.0", + "redis": "^4.6.15", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.0.1", - "uuid": "^9.0.0", + "typedoc-plugin-markdown": "^4.2.1", + "uuid": "^10.0.0", "validator": "^13.12.0", "winston": "^3.13.0", - "ws": "^8.17.0", + "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", "zod-error": "^1.5.0" }, "devDependencies": { "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.7", - "@graphql-codegen/typescript-resolvers": "^4.1.0", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-resolvers": "^4.2.0", "@graphql-eslint/eslint-plugin": "^3.20.1", "@parcel/watcher": "^2.4.1", "@types/bcryptjs": "^2.4.6", @@ -114,29 +114,29 @@ "@types/i18n": "^0.13.12", "@types/inquirer": "^9.0.7", "@types/jsonwebtoken": "^9.0.5", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.6", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^20.12.7", + "@types/node": "^20.14.9", "@types/nodemailer": "^6.4.15", "@types/uuid": "^9.0.7", - "@types/validator": "^13.11.10", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@types/validator": "^13.12.0", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.16.0", "@vitest/coverage-v8": "^1.6.0", "cls-bluebird": "^2.1.0", "concurrently": "^8.2.2", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-tsdoc": "^0.2.17", + "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", "graphql-markdown": "^7.0.0", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", + "husky": "^9.1.1", + "lint-staged": "^15.2.7", "prettier": "^3.2.5", - "rimraf": "^5.0.7", - "tsx": "^4.7.3", + "rimraf": "^6.0.1", + "tsx": "^4.16.2", "typescript": "^5.4.5", "vitest": "^1.2.1" }, diff --git a/schema.graphql b/schema.graphql index e05f855023..f605000c6a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -163,6 +163,19 @@ type AuthData { user: User! } +enum CampaignOrderByInput { + endDate_ASC + endDate_DESC + fundingGoal_ASC + fundingGoal_DESC + startDate_ASC + startDate_DESC +} + +input CampaignWhereInput { + name_contains: String +} + type CheckIn { _id: ID! createdAt: DateTime! @@ -323,7 +336,7 @@ input CreateUserTagInput { name: String! organizationId: ID! parentTagId: ID - tagColor: String! + tagColor: String } enum Currency { @@ -526,7 +539,7 @@ type DirectChat { createdAt: DateTime! creator: User messages: [DirectChatMessage] - organization: Organization! + organization: Organization updatedAt: DateTime! users: [User!]! } @@ -809,7 +822,7 @@ enum Frequency { type Fund { _id: ID! - campaigns: [FundraisingCampaign!] + campaigns: [FundraisingCampaign] createdAt: DateTime! creator: User isArchived: Boolean! @@ -848,6 +861,11 @@ input FundInput { taxDeductible: Boolean! } +enum FundOrderByInput { + createdAt_ASC + createdAt_DESC +} + input FundWhereInput { name_contains: String } @@ -897,6 +915,7 @@ type GroupChat { creator: User messages: [GroupChatMessage] organization: Organization! + title: String! updatedAt: DateTime! users: [User!]! } @@ -1308,6 +1327,15 @@ enum PaginationDirection { scalar PhoneNumber +enum PledgeOrderByInput { + amount_ASC + amount_DESC + endDate_ASC + endDate_DESC + startDate_ASC + startDate_DESC +} + type Plugin { _id: ID! pluginCreatedBy: String! @@ -1434,17 +1462,20 @@ type Query { adminPlugin(orgId: ID!): [Plugin] advertisementsConnection(after: String, before: String, first: PositiveInt, last: PositiveInt): AdvertisementsConnection agendaCategory(id: ID!): AgendaCategory! + agendaItemByEvent(relatedEventId: ID!): [AgendaItem] + agendaItemByOrganization(organizationId: ID!): [AgendaItem] agendaItemCategoriesByOrganization(organizationId: ID!): [AgendaCategory] checkAuth: User! customDataByOrganization(organizationId: ID!): [UserCustomData!]! customFieldsByOrganization(id: ID!): [OrganizationCustomField] + directChatById(id: ID!): DirectChat directChatsByUserID(id: ID!): [DirectChat] directChatsMessagesByChatID(id: ID!): [DirectChatMessage] event(id: ID!): Event eventVolunteersByEvent(id: ID!): [EventVolunteer] eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]! - fundsByOrganization(organizationId: ID!, where: FundWhereInput): [Fund] + fundsByOrganization(orderBy: FundOrderByInput, organizationId: ID!, where: FundWhereInput): [Fund] getAgendaItem(id: ID!): AgendaItem getAgendaSection(id: ID!): AgendaSection getAllAgendaItems: [AgendaItem] @@ -1456,13 +1487,15 @@ type Query { getEventAttendee(eventId: ID!, userId: ID!): EventAttendee getEventAttendeesByEventId(eventId: ID!): [EventAttendee] getEventInvitesByUserId(userId: ID!): [EventAttendee!]! - getFundById(id: ID!): Fund! - getFundraisingCampaignById(id: ID!): FundraisingCampaign! + getFundById(id: ID!, orderBy: CampaignOrderByInput, where: CampaignWhereInput): Fund! + getFundraisingCampaignById(id: ID!, orderBy: PledgeOrderByInput): FundraisingCampaign! getFundraisingCampaignPledgeById(id: ID!): FundraisingCampaignPledge! getNoteById(id: ID!): Note! getPlugins: [Plugin] getVenueByOrgId(first: Int, orderBy: VenueOrderByInput, orgId: ID!, skip: Int, where: VenueWhereInput): [Venue] getlanguage(lang_code: String!): [Translation] + groupChatById(id: ID!): GroupChat + groupChatsByUserId(id: ID!): [GroupChat] hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean isSampleOrganization(id: ID!): Boolean! joinedOrganizations(id: ID): [Organization] @@ -1564,8 +1597,8 @@ enum Status { type Subscription { directMessageChat: MessageChat - messageSentToDirectChat: DirectChatMessage - messageSentToGroupChat: GroupChatMessage + messageSentToDirectChat(userId: ID!): DirectChatMessage + messageSentToGroupChat(userId: ID!): GroupChatMessage onPluginUpdate: Plugin } @@ -1638,7 +1671,6 @@ input UpdateAgendaItemInput { relatedEvent: ID sequence: Int title: String - updatedBy: ID! urls: [String] users: [ID] } @@ -1700,6 +1732,7 @@ input UpdateFundCampaignPledgeInput { currency: Currency endDate: Date startDate: Date + users: [ID] } input UpdateFundInput { @@ -1884,6 +1917,7 @@ type UserTag { type UserTagsConnection { edges: [UserTagsConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! + totalCount: PositiveInt } """A default connection edge on the UserTag type for UserTagsConnection.""" @@ -1979,7 +2013,7 @@ enum WeekDays { } input createChatInput { - organizationId: ID! + organizationId: ID userIds: [ID!]! } diff --git a/src/app.ts b/src/app.ts index 5479ad7fd0..a5571597a4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,35 +10,41 @@ import path from "path"; import { appConfig } from "./config"; import { requestContext, requestTracing, stream } from "./libraries"; import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.mjs"; + const app = express(); +// Middleware for tracing requests app.use(requestTracing.middleware()); + +// Initialize i18n for internationalization app.use(i18n.init); +// Rate limiting middleware to prevent abuse const apiLimiter = rateLimit({ - windowMs: 60 * 60 * 1000, - max: 50000, + windowMs: 60 * 60 * 1000, // 1 hour window + max: 50000, // limit each IP to 50000 requests per windowMs message: "Too many requests from this IP, please try again after 15 minutes", }); +app.use(apiLimiter); // eslint-disable-next-line @typescript-eslint/no-unused-vars const corsOptions: cors.CorsOptions = { origin: (origin, next) => { if (process.env.NODE_ENV === "development") { - next(null, true); + next(null, true); // Allow all origins in development return; } else if (process.env.NODE_ENV === "production") { const talawaAdmin = process.env.TALAWA_ADMIN_URL; if (origin === talawaAdmin) { - next(null, true); + next(null, true); // Allow only specific origin in production return; } } - - next(new Error("Unauthorized")); + next(new Error("Unauthorized")); // Reject other origins }, }; +// Configure i18n settings i18n.configure({ directory: `${__dirname}/../locales`, staticCatalog: { @@ -55,45 +61,58 @@ i18n.configure({ updateFiles: process.env.NODE_ENV !== "production", syncFiles: process.env.NODE_ENV !== "production", }); - app.use(i18n.init); -app.use(apiLimiter); + +// Helmet middleware for security headers app.use( helmet({ contentSecurityPolicy: - process.env.NODE_ENV === "production" ? undefined : false, + process.env.NODE_ENV === "production" ? undefined : false, // Disable CSP in development }), ); + +// Sanitize data to prevent MongoDB operator injection app.use(mongoSanitize()); app.use(cors()); +// Serve static files with Cross-Origin-Resource-Policy header set app.use("/images", (req, res, next) => { res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); next(); }); +// Parse JSON requests with a size limit of 50mb app.use(express.json({ limit: "50mb" })); + +// Handle file uploads using graphql-upload app.use(graphqlUploadExpress()); + +// Parse URL-encoded requests with a size limit of 50mb app.use(express.urlencoded({ limit: "50mb", extended: true })); -// Fix added to stream +// Request logging middleware using Morgan app.use( requestLogger( ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms', { - stream: stream, + stream: stream, // Stream logs to a defined stream (e.g., file, console) }, ), ); +// Serve static files for images and videos app.use("/images", express.static(path.join(__dirname, "./../images"))); app.use("/videos", express.static(path.join(__dirname, "./../videos"))); +// Middleware for managing request context (e.g., user session) app.use(requestContext.middleware()); -if (process.env.NODE_ENV !== "production") +// Enable GraphQL Voyager visualization in development +if (process.env.NODE_ENV !== "production") { app.use("/voyager", voyagerMiddleware({ endpointUrl: "/graphql" })); +} +// Endpoint to check the health status of the application app.get("/", (req, res) => res.json({ "talawa-version": "v1", diff --git a/src/checks.ts b/src/checks.ts index 6ae11693ab..f5c602df22 100644 --- a/src/checks.ts +++ b/src/checks.ts @@ -5,36 +5,50 @@ import { getEnvIssues } from "./env"; import { logger } from "./libraries"; import { connect } from "./db"; -// Function to log warnings for super admin environment variable +/** + * Logs warnings regarding the LAST_RESORT_SUPERADMIN_EMAIL environment variable. + * This function checks if a super admin exists and if the LAST_RESORT_SUPERADMIN_EMAIL variable is present in the .env file. + * Depending on the conditions, appropriate warnings are logged. + */ const logWarningForSuperAdminEnvVariable = async (): Promise => { // Connect to the database await connect(); try { + // Check if there is an existing super admin profile const superAdminExist = await AppUserProfile.exists({ isSuperAdmin: true }); + // Check if the LAST_RESORT_SUPERADMIN_EMAIL variable is present in the .env file const isVariablePresentInEnvFile = !!LAST_RESORT_SUPERADMIN_EMAIL; if (superAdminExist && isVariablePresentInEnvFile) { + // Log a warning if both conditions are met logger.warn( "\x1b[1m\x1b[33m%s\x1b[0m", "The LAST_RESORT_SUPERADMIN_EMAIL variable configured in your .env file poses a security risk. We strongly recommend that you remove it if not required. Please refer to the documentation in the INSTALLATION.md file.You have created super admin, please remove the LAST_RESORT_SUPERADMIN_EMAIL variable from .env file if you don't require it", ); } else if (!superAdminExist && !isVariablePresentInEnvFile) { + // Log a warning if neither condition is met logger.warn( "\x1b[1m\x1b[33m%s\x1b[0m", "To create your first Super Admin, the LAST_RESORT_SUPERADMIN_EMAIL parameter needs to be set in the .env file. Please refer to the documentation in the INSTALLATION.md file.", ); } } catch (error) { + // Log an error if there's an exception while checking for super admin existence logger.error("Error checking for super admin existence:", error); } }; -// Function to log environment variable issues +/** + * Logs issues related to environment variables. + * This function logs any issues found with environment variables in the .env file. + * It also logs warnings regarding the LAST_RESORT_SUPERADMIN_EMAIL variable. + */ export const logIssues = async (): Promise => { try { - // Log all the environment variable issues + // Log all environment variable issues const issues = getEnvIssues(); if (issues) { + // Log errors if there are issues with environment variables logger.error( "Invalid environment variables found in your .env file, check the errors below!", ); @@ -44,12 +58,14 @@ export const logIssues = async (): Promise => { }), ); } else { + // Log info message if environment variables are valid logger.info("The environment variables are valid!"); } - // Log the SuperAdmin environment variable issue (if any) + // Log warnings regarding the LAST_RESORT_SUPERADMIN_EMAIL variable await logWarningForSuperAdminEnvVariable(); } catch (error) { + // Log an error if there's an exception while logging environment variable issues logger.error("Error logging environment variable issues:", error); } }; diff --git a/src/config/appConfig.ts b/src/config/appConfig.ts index 45ef049afb..c2d511a5ca 100644 --- a/src/config/appConfig.ts +++ b/src/config/appConfig.ts @@ -1,7 +1,20 @@ +/** + * Application configuration settings. + * This object contains various configuration options for the application. + */ export const appConfig = { + /** The current environment of the application (e.g., 'development', 'production'). */ env: process.env.NODE_ENV, + + /** Determines if logs should be colorized. */ colorize_logs: process.env.COLORIZE_LOGS, + + /** The logging level for the application (e.g., 'info', 'error'). */ log_level: process.env.LOG_LEVEL, + + /** The default language for the application. */ defaultLocale: "en", + + /** An array of supported language for the application. */ supportedLocales: ["hi", "en", "zh", "fr", "sp"], }; diff --git a/src/config/index.ts b/src/config/index.ts index 59c19e13af..9189124765 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1 +1,5 @@ +/** + * Exports all configurations from the appConfig module. + * This allows other modules to import configurations easily. + */ export * from "./appConfig"; diff --git a/src/config/plugins/loadPlugins.ts b/src/config/plugins/loadPlugins.ts index 06c1284731..69cb2cafaa 100644 --- a/src/config/plugins/loadPlugins.ts +++ b/src/config/plugins/loadPlugins.ts @@ -3,31 +3,60 @@ import _ from "lodash"; import pluginData from "./pluginData.json"; import { logger } from "../../libraries"; import mongoose from "mongoose"; -// Only loads plugin data for the time if it's not currently present in the database + +/** + * Loads plugin data into the MongoDB database if it is not already present. + * + * This function connects to the MongoDB database using the connection URL specified in the environment variables. + * It checks if the plugin data already exists in the database. If the data does not exist, it inserts the data from + * the provided JSON file (`pluginData.json`). If the data is already present, it logs a message indicating so. + * + * @example + * ```typescript + * import loadPlugins from './path/to/loadPlugins'; + * + * loadPlugins().then(() => { + * console.log('Plugins loaded successfully.'); + * }).catch(error => { + * console.error('Error loading plugins:', error); + * }); + * ``` + * @see Parent File: + * - `src/index.ts` + * + * @returns A promise that resolves when the plugins have been loaded or confirms that they are already present. + * + */ + const loadPlugins = async (): Promise => { try { + // Connect to the MongoDB database await mongoose.connect(process.env.MONGO_DB_URL as string); logger.info("\x1b[1m\x1b[32m%s\x1b[0m", `Connected to the database`); + + // Fetch existing plugins from the database const res = await Plugin.find(); const databaseTitle = mongoose.connection.db.databaseName; + if (_.isEmpty(res)) { - //no previous data then update with our new data. (Only happens once) + // No previous data, so insert new plugin data from JSON file // eslint-disable-next-line @typescript-eslint/no-explicit-any pluginData.forEach(async (plugin: any) => { await Plugin.create(plugin); }); logger.info( "\x1b[1m\x1b[32m%s\x1b[0m", - `Uploaded Plugins in ${databaseTitle} `, + `Uploaded Plugins in ${databaseTitle}`, ); } else { - //plugin data already present + // Plugin data is already present in the database logger.info( "\x1b[1m\x1b[32m%s\x1b[0m", `Plugin data already present at ${databaseTitle}`, ); } } catch (error) { + // Log any errors that occur during the process logger.error(error); } }; diff --git a/src/constants.ts b/src/constants.ts index b998b0ef8b..fab3f2b12d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,478 +8,478 @@ if (!issues) { ENV = envSchema.parse(process.env); } -export const ACTION_ITEM_NOT_FOUND_ERROR = { +export const ACTION_ITEM_NOT_FOUND_ERROR = Object.freeze({ DESC: "ActionItem not found", CODE: "actionItem.notFound", MESSAGE: "actionItem.notFound", PARAM: "actionItem", -}; +}); -export const ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR = { +export const ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR = Object.freeze({ DESC: "ActionItemCategory not found", CODE: "actionItemCategory.notFound", MESSAGE: "actionItemCategory.notFound", PARAM: "actionItemCategory", -}; +}); -export const ACTION_ITEM_CATEGORY_ALREADY_EXISTS = { +export const ACTION_ITEM_CATEGORY_ALREADY_EXISTS = Object.freeze({ DESC: "Action Item Category already exists", CODE: "actionItemCategory.alreadyExists", MESSAGE: "actionItemCategory.alreadyExists", PARAM: "actionItemCategory", -}; +}); -export const ACTION_ITEM_CATEGORY_IS_DISABLED = { +export const ACTION_ITEM_CATEGORY_IS_DISABLED = Object.freeze({ DESC: "Action Item Category is disabled", CODE: "actionItemCategory.isDisabled", MESSAGE: "actionItemCategory.isDisabled", PARAM: "actionItemCategory", -}; +}); -export const AGENDA_CATEGORY_NOT_FOUND_ERROR = { +export const AGENDA_CATEGORY_NOT_FOUND_ERROR = Object.freeze({ DESC: "Agenda category not found", CODE: "agendaCategory.notFound", MESSAGE: "agendaCategory.notFound", PARAM: "agendaCategory", -}; +}); -export const BASE_RECURRING_EVENT_NOT_FOUND = { +export const BASE_RECURRING_EVENT_NOT_FOUND = Object.freeze({ DESC: "Base Recurring Event not found", CODE: "baseRecurringEvent.notFound", MESSAGE: "baseRecurringEvent.notFound", PARAM: "baseRecurringEvent", -}; +}); -export const CHAT_NOT_FOUND_ERROR = { +export const CHAT_NOT_FOUND_ERROR = Object.freeze({ DESC: "Chat not found", CODE: "chat.notFound", MESSAGE: "chat.notFound", PARAM: "chat", -}; +}); -export const VENUE_ALREADY_EXISTS_ERROR = { +export const VENUE_ALREADY_EXISTS_ERROR = Object.freeze({ DESC: "Venue already exists", CODE: "venue.alreadyExists", MESSAGE: "venue.alreadyExists", PARAM: "venue", -}; +}); -export const VENUE_NOT_FOUND_ERROR = { +export const VENUE_NOT_FOUND_ERROR = Object.freeze({ DESC: "Venue does not exist", CODE: "venue.NotFound", MESSAGE: "venue.NotFound", PARAM: "venue", -}; +}); -export const COMMENT_NOT_FOUND_ERROR = { +export const COMMENT_NOT_FOUND_ERROR = Object.freeze({ DESC: "Comment not found", CODE: "comment.notFound", MESSAGE: "comment.notFound", PARAM: "comment", -}; +}); -export const COMMUNITY_LOGO_NOT_MISSING_IN_ARGS = { +export const COMMUNITY_LOGO_NOT_MISSING_IN_ARGS = Object.freeze({ DESC: "Community logo was not provided", CODE: "community.logoMissing", MESSAGE: "community.logoMissing", PARAM: "community", -}; +}); export const ERROR_IN_SENDING_MAIL = "Error in sending mail"; -export const EVENT_NOT_FOUND_ERROR = { +export const EVENT_NOT_FOUND_ERROR = Object.freeze({ DESC: "Event not found", CODE: "event.notFound", MESSAGE: "event.notFound", PARAM: "event", -}; +}); -export const FEEDBACK_ALREADY_SUBMITTED = { +export const FEEDBACK_ALREADY_SUBMITTED = Object.freeze({ MESSAGE: "The user has already submitted a feedback for this event.", CODE: "feedback.alreadySubmitted", PARAM: "feedback.alreadySubmitted", -}; -export const FUND_ALREADY_EXISTS = { +}); +export const FUND_ALREADY_EXISTS = Object.freeze({ DESC: "Fund already exists", CODE: "fund.alreadyExists", MESSAGE: "fund.alreadyExists", PARAM: "fund", -}; -export const FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR = { +}); +export const FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR = Object.freeze({ DESC: "FundraisingCampaign not found", CODE: "fundraisingCampaign.notFound", MESSAGE: "fundraisingCampaign.notFound", PARAM: "fundraisingCampaign", -}; -export const FUNDRAISING_CAMPAIGN_ALREADY_EXISTS = { +}); +export const FUNDRAISING_CAMPAIGN_ALREADY_EXISTS = Object.freeze({ DESC: "Fundraising Campaign already exists", CODE: "fundraisingCampaign.alreadyExists", MESSAGE: "fundraisingCampaign.alreadyExists", PARAM: "fundraisingCampaign", -}; -export const FUNDRAISING_CAMPAIGN_ALREADY_ADDED = { +}); +export const FUNDRAISING_CAMPAIGN_ALREADY_ADDED = Object.freeze({ DESC: "Fundraising Campaign already added", CODE: "fundraisingCampaign.alreadyAdded", MESSAGE: "fundraisingCampaign.alreadyAdded", PARAM: "fundraisingCampaign", -}; -export const FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR = { +}); +export const FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR = Object.freeze({ DESC: "FundraisingCampaignPledge not found", CODE: "fundraisingCampaignPledge.notFound", MESSAGE: "fundraisingCampaignPledge.notFound", PARAM: "fundraisingCampaignPledge", -}; -export const FUNDRAISING_CAMPAIGN_PLEDGE_ALREADY_EXISTS = { +}); +export const FUNDRAISING_CAMPAIGN_PLEDGE_ALREADY_EXISTS = Object.freeze({ DESC: "Fundraising Campaign Pledge already exists", CODE: "fundraisingCampaignPledge.alreadyExists", MESSAGE: "fundraisingCampaignPledge.alreadyExists", PARAM: "fundraisingCampaignPledge", -}; -export const FUND_NOT_FOUND_ERROR = { +}); +export const FUND_NOT_FOUND_ERROR = Object.freeze({ DESC: "Fund not found", CODE: "fund.notFound", MESSAGE: "fund.notFound", PARAM: "fund", -}; +}); export const INVALID_OTP = "Invalid OTP"; export const IN_PRODUCTION = process.env.NODE_ENV === "production"; -export const MEMBER_NOT_FOUND_ERROR = { +export const MEMBER_NOT_FOUND_ERROR = Object.freeze({ DESC: "Member not found", CODE: "member.notFound", MESSAGE: "member.notFound", PARAM: "member", -}; -export const MEMBERSHIP_REQUEST_NOT_FOUND_ERROR = { +}); +export const MEMBERSHIP_REQUEST_NOT_FOUND_ERROR = Object.freeze({ DESC: "Membership Request not found", CODE: "membershipRequest.notFound", MESSAGE: "membershipRequest.notFound", PARAM: "membershipRequest", -}; +}); -export const MEMBERSHIP_REQUEST_ALREADY_EXISTS = { +export const MEMBERSHIP_REQUEST_ALREADY_EXISTS = Object.freeze({ DESC: "Membership Request already exists", CODE: "membershipRequest.alreadyExists", MESSAGE: "membershipRequest.alreadyExists", PARAM: "membershipRequest", -}; +}); -export const ORGANIZATION_MEMBER_NOT_FOUND_ERROR = { +export const ORGANIZATION_MEMBER_NOT_FOUND_ERROR = Object.freeze({ DESC: "Organization's user is not a member", CODE: "organization.member.notFound", MESSAGE: "organization.member.notFound", PARAM: "organizationMember", -}; -export const ORGANIZATION_NOT_AUTHORIZED_ERROR = { +}); +export const ORGANIZATION_NOT_AUTHORIZED_ERROR = Object.freeze({ DESC: "Organization is not authorized", CODE: "org.notAuthorized", MESSAGE: "org.notAuthorized", PARAM: "org", -}; -export const ORGANIZATION_NOT_FOUND_ERROR = { +}); +export const ORGANIZATION_NOT_FOUND_ERROR = Object.freeze({ DESC: "Organization not found", CODE: "organization.notFound", MESSAGE: "organization.notFound", PARAM: "organization", -}; +}); -export const RECURRENCE_RULE_NOT_FOUND = { +export const RECURRENCE_RULE_NOT_FOUND = Object.freeze({ DESC: "Recurrence Rule not found", CODE: "recurrenceRule.notFound", MESSAGE: "recurrenceRule.notFound", PARAM: "recurrenceRule", -}; +}); -export const VENUE_NAME_MISSING_ERROR = { +export const VENUE_NAME_MISSING_ERROR = Object.freeze({ DESC: "Venue name not found", CODE: "venueName.notFound", MESSAGE: "venueName.notFound", PARAM: "venueName", -}; +}); -export const VENUE_ALREADY_SCHEDULED = { +export const VENUE_ALREADY_SCHEDULED = Object.freeze({ DESC: "Venue is already scheduled", CODE: "venue.alreadySchduled", MESSAGE: "venue.alreadySchduled", PARAM: "venue", -}; +}); -export const ORGANIZATION_IMAGE_NOT_FOUND_ERROR = { +export const ORGANIZATION_IMAGE_NOT_FOUND_ERROR = Object.freeze({ DESC: "OrganizationImage not found", CODE: "organizationImage.notFound", MESSAGE: "organizationImage.notFound", PARAM: "organizationImage", -}; -export const PLUGIN_NOT_FOUND = { +}); +export const PLUGIN_NOT_FOUND = Object.freeze({ DESC: "Plugin not found", CODE: "plugin.notFound", MESSAGE: "plugin.notFound", PARAM: "plugin", -}; -export const POST_NOT_FOUND_ERROR = { +}); +export const POST_NOT_FOUND_ERROR = Object.freeze({ DESC: "Post not found", CODE: "post.notFound", MESSAGE: "post.notFound", PARAM: "post", -}; -export const REGISTRANT_ALREADY_EXIST_ERROR = { +}); +export const REGISTRANT_ALREADY_EXIST_ERROR = Object.freeze({ DESC: "Already registered for the event", CODE: "registrant.alreadyExist", MESSAGE: "registrant.alreadyExist", PARAM: "registrant", -}; +}); -export const INVALID_FILE_TYPE = { +export const INVALID_FILE_TYPE = Object.freeze({ MESSAGE: "invalid.fileType", CODE: "internalServerError", PARAM: "internalServerError", -}; +}); -export const IMAGE_SIZE_LIMIT_KB = { +export const IMAGE_SIZE_LIMIT_KB = Object.freeze({ MESSAGE: "The Image Size Limit has been exceeded", CODE: "internalServerError", PARAM: "internalServerError", -}; +}); -export const INVALID_ROLE_TYPE = { +export const INVALID_ROLE_TYPE = Object.freeze({ DESC: "Invalid Role Type", MESSAGE: "invalid.roleType", CODE: "internalServerError", PARAM: "internalServerError", -}; +}); -export const SAME_FILE_ERROR = { +export const SAME_FILE_ERROR = Object.freeze({ MESSAGE: "The newer image is the same as the previous image in the database", CODE: "internalServerError", PARAM: "internalServerError", -}; +}); -export const INTERNAL_SERVER_ERROR = { +export const INTERNAL_SERVER_ERROR = Object.freeze({ MESSAGE: "Internal Server Error!", CODE: "internalServerError", PARAM: "internalServerError", -}; +}); -export const UNAUTHENTICATED_ERROR = { +export const UNAUTHENTICATED_ERROR = Object.freeze({ MESSAGE: "UnauthenticatedError", CODE: "user.notAuthenticated", PARAM: "userAuthentication", -}; +}); -export const END_DATE_VALIDATION_ERROR = { +export const END_DATE_VALIDATION_ERROR = Object.freeze({ MESSAGE: "Error: End date must be greater than or equal to start date.", CODE: "enddate.notvalid", PARAM: "dateValidation", -}; +}); -export const START_DATE_VALIDATION_ERROR = { +export const START_DATE_VALIDATION_ERROR = Object.freeze({ MESSAGE: "Error: Start date must be greater than or equal to current date.", CODE: "startdate.notvalid", PARAM: "dateValidation", -}; +}); -export const FIELD_NON_EMPTY_ERROR = { +export const FIELD_NON_EMPTY_ERROR = Object.freeze({ MESSAGE: "Error: Field cannot be null, an empty string, or contain only spaces.", CODE: "field_non_empty_error", PARAM: "field", -}; +}); -export const LENGTH_VALIDATION_ERROR = { +export const LENGTH_VALIDATION_ERROR = Object.freeze({ MESSAGE: "Error: Length must be greater than 0 and less than", CODE: "string.notValid", PARAM: "stringValidation", -}; +}); -export const USER_FAMILY_MIN_MEMBERS_ERROR_CODE = { +export const USER_FAMILY_MIN_MEMBERS_ERROR_CODE = Object.freeze({ MESSAGE: "InputValidationError", CODE: "membersInUserFamilyLessThanOne", PARAM: "membersInUserFamilyLessThanOne", -}; +}); -export const REGEX_VALIDATION_ERROR = { +export const REGEX_VALIDATION_ERROR = Object.freeze({ MESSAGE: "Error: Entered value must be a valid string", CODE: "string.notValid", PARAM: "stringValidation", -}; +}); -export const USER_FAMILY_NOT_FOUND_ERROR = { +export const USER_FAMILY_NOT_FOUND_ERROR = Object.freeze({ MESSAGE: "Error: User Family Not Found", CODE: "userfamilyNotFound", PARAM: "userfamilyNotFound", -}; +}); -export const USER_NOT_AUTHORIZED_SUPERADMIN = { +export const USER_NOT_AUTHORIZED_SUPERADMIN = Object.freeze({ MESSAGE: "Error: Current user must be a SUPERADMIN", CODE: "role.notValid.superadmin", PARAM: "roleValidationSuperAdmin", -}; +}); -export const USER_NOT_AUTHORIZED_ADMIN = { +export const USER_NOT_AUTHORIZED_ADMIN = Object.freeze({ MESSAGE: "Error: Current user must be an ADMIN or a SUPERADMIN", CODE: "role.notValid.admin", PARAM: "roleValidationAdmin", -}; +}); -export const USER_ALREADY_REGISTERED_FOR_EVENT = { +export const USER_ALREADY_REGISTERED_FOR_EVENT = Object.freeze({ MESSAGE: "The user has already been registered for the event", CODE: "user.alreadyRegistered", PARAM: "user.alreadyRegistered", -}; +}); -export const USER_ALREADY_INVITED_FOR_EVENT = { +export const USER_ALREADY_INVITED_FOR_EVENT = Object.freeze({ MESSAGE: "The user has already been invited for the event", CODE: "user.alreadyInvited", PARAM: "user.alreadyInvited", -}; +}); -export const USER_NOT_REGISTERED_FOR_EVENT = { +export const USER_NOT_REGISTERED_FOR_EVENT = Object.freeze({ MESSAGE: "The user is not registered for the event", CODE: "user.notRegistered", PARAM: "user.notRegistered", -}; +}); -export const USER_NOT_CHECKED_IN = { +export const USER_NOT_CHECKED_IN = Object.freeze({ MESSAGE: "The user did not check in for the event.", CODE: "user.notCheckedIn", PARAM: "user.notCheckedIn", -}; +}); -export const USER_NOT_ORGANIZATION_ADMIN = { +export const USER_NOT_ORGANIZATION_ADMIN = Object.freeze({ MESSAGE: "Error: User must be an ADMIN", CODE: "role.notValid.admin", PARAM: "roleValidationAdmin", -}; +}); -export const USER_BLOCKING_SELF = { +export const USER_BLOCKING_SELF = Object.freeze({ MESSAGE: "Error: Current user cannot block self", CODE: "user.selfBlock", PARAM: "userSelfBlock", -}; +}); -export const USER_REMOVING_SELF = { +export const USER_REMOVING_SELF = Object.freeze({ MESSAGE: "Error: Current user cannot remove self, instead you can use leave Org function", CODE: "user.selfRemove", PARAM: "userSelfRemove", -}; +}); -export const ADMIN_REMOVING_ADMIN = { +export const ADMIN_REMOVING_ADMIN = Object.freeze({ MESSAGE: "Error: Current admin cannot remove another admin", CODE: "admin.removeAdmin", PARAM: "admin.removeAdmin", -}; +}); -export const ADMIN_REMOVING_CREATOR = { +export const ADMIN_REMOVING_CREATOR = Object.freeze({ MESSAGE: "Error: Current admin cannot remove the creator of the Org", CODE: "admin.removeCreator", PARAM: "admin.removeCreator", -}; +}); -export const ADMIN_CHANGING_ROLE_OF_CREATOR = { +export const ADMIN_CHANGING_ROLE_OF_CREATOR = Object.freeze({ MESSAGE: "Error: Current admin cannot change the role of the creator of the Org", CODE: "admin.changeRoleOfCreator", PARAM: "admin.changeRoleOfCreator", -}; +}); -export const ADMIN_CANNOT_CHANGE_ITS_ROLE = { +export const ADMIN_CANNOT_CHANGE_ITS_ROLE = Object.freeze({ MESSAGE: "Error: Current admin cannot change its own role", CODE: "admin.changeOwnRole", PARAM: "admin.changeOwnRole", -}; +}); -export const POST_NEEDS_TO_BE_PINNED = { +export const POST_NEEDS_TO_BE_PINNED = Object.freeze({ MESSAGE: "Post needs to be pinned inorder to add a title", CODE: "post.notAllowedToAddTitle", PARAM: "post.notAllowedToAddTitle", -}; +}); -export const PLEASE_PROVIDE_TITLE = { +export const PLEASE_PROVIDE_TITLE = Object.freeze({ MESSAGE: "Please provide a title to pin post", CODE: "post.provideTitle", PARAM: "post.provideTitle", -}; +}); -export const USER_NOT_AUTHORIZED_TO_PIN = { +export const USER_NOT_AUTHORIZED_TO_PIN = Object.freeze({ MESSAGE: "The user must be a superadmin or an admin of the organization to pin/unpin posts", CODE: "user.notAuthorizedToPin", PARAM: "user.notAuthorizedToPin", -}; +}); -export const TAG_NOT_FOUND = { +export const TAG_NOT_FOUND = Object.freeze({ MESSAGE: "The tag with the specified ID doesn't exist.", CODE: "tag.doesNotExist", PARAM: "tag.doesNotExist", -}; +}); -export const USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION = { +export const USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION = Object.freeze({ MESSAGE: "The user to which the tag is being assigned hasn't joined the tag's parent organization.", CODE: "user.notJoinedOrg", PARAM: "user.notJoinedOrg", -}; +}); -export const INVALID_TAG_INPUT = { +export const INVALID_TAG_INPUT = Object.freeze({ MESSAGE: "Either an organizatin ID or a parent tag ID must be provided for this operation.", CODE: "invalidArgs", PARAM: "invalidArgs", -}; +}); -export const INCORRECT_TAG_INPUT = { +export const INCORRECT_TAG_INPUT = Object.freeze({ MESSAGE: "The tag does not belong to the organization provided.", CODE: "invalidArgs.tag", PARAM: "invalidArgs.tag", -}; +}); -export const NO_CHANGE_IN_TAG_NAME = { +export const NO_CHANGE_IN_TAG_NAME = Object.freeze({ MESSAGE: "The tag name is the already set to the value it is being requested to be changed to.", CODE: "invalidArgs.tagName", PARAM: "invalidArgs.tagName", -}; +}); -export const TAG_ALREADY_EXISTS = { +export const TAG_ALREADY_EXISTS = Object.freeze({ MESSAGE: "A tag with the same name and the same parent tag already exists for this organization.", CODE: "tag.alreadyExists", PARAM: "tag.alreadyExists", -}; +}); -export const USER_NOT_AUTHORIZED_TO_CREATE_TAG = { +export const USER_NOT_AUTHORIZED_TO_CREATE_TAG = Object.freeze({ MESSAGE: "The user must be a superadmin or an admin of the organization to create the tag.", CODE: "user.notAuth.createTag", PARAM: "user.notAuth.createTag", -}; +}); -export const USER_ALREADY_HAS_TAG = { +export const USER_ALREADY_HAS_TAG = Object.freeze({ MESSAGE: "The user already has the tag that it is being requested to assigned.", CODE: "user.alreadyHasTag", PARAM: "user.alreadyHasTag", -}; +}); -export const USER_DOES_NOT_HAVE_THE_TAG = { +export const USER_DOES_NOT_HAVE_THE_TAG = Object.freeze({ MESSAGE: "The user does not have the tag that is being requested to be removed.", CODE: "user.doesNotHaveTag", PARAM: "user.doesNotHaveTag", -}; +}); -export const ADVERTISEMENT_NOT_FOUND_ERROR = { +export const ADVERTISEMENT_NOT_FOUND_ERROR = Object.freeze({ DESC: "Advertisement not found", CODE: "advertisement.notFound", MESSAGE: "advertisement.notFound", PARAM: "advertisement", -}; -export const INPUT_NOT_FOUND_ERROR = { +}); +export const INPUT_NOT_FOUND_ERROR = Object.freeze({ MESSAGE: "Input not found", CODE: "Input.required", PARAM: "advertisement", -}; +}); export const STATUS_ACTIVE = "ACTIVE"; export const URL = @@ -487,214 +487,214 @@ export const URL = ? "http://localhost:4000/graphql" : "http://calico.palisadoes.org/talawa/graphql"; -export const USER_ALREADY_MEMBER_ERROR = { +export const USER_ALREADY_MEMBER_ERROR = Object.freeze({ DESC: "User is already a member", CODE: "user.alreadyMember", MESSAGE: "user.alreadyMember", PARAM: "user", -}; -export const USER_ALREADY_UNREGISTERED_ERROR = { +}); +export const USER_ALREADY_UNREGISTERED_ERROR = Object.freeze({ DESC: "Already registered for the event", CODE: "registrant.alreadyUnregistered", MESSAGE: "registrant.alreadyUnregistered", PARAM: "registrant", -}; -export const USER_NOT_AUTHORIZED_ERROR = { +}); +export const USER_NOT_AUTHORIZED_ERROR = Object.freeze({ DESC: "User is not authorized for performing this operation", CODE: "user.notAuthorized", MESSAGE: "user.notAuthorized", PARAM: "user", -}; +}); -export const UNAUTHORIZED_REMOVE_AGENDA_ITEM_ERROR = { +export const UNAUTHORIZED_REMOVE_AGENDA_ITEM_ERROR = Object.freeze({ DESC: "Unauthorized to remove the agenda item", CODE: "unauthorized.removeAgendaItem", MESSAGE: "Unauthorized to remove the agenda item", PARAM: "agendaItem", -}; +}); -export const UNAUTHORIZED_UPDATE_AGENDA_ITEM_ERROR = { +export const UNAUTHORIZED_UPDATE_AGENDA_ITEM_ERROR = Object.freeze({ DESC: "Unauthorized to update the agenda item", CODE: "unauthorized.updateAgendaItem", MESSAGE: "Unauthorized to update the agenda item", PARAM: "agendaItem", -}; +}); -export const AGENDA_ITEM_NOT_FOUND_ERROR = { +export const AGENDA_ITEM_NOT_FOUND_ERROR = Object.freeze({ DESC: "Agenda item not found", CODE: "agendaItem.notFound", MESSAGE: "agendaItem.notFound", PARAM: "agendaItem", -}; -export const AGENDA_ITEM_CREATION_ERROR = { +}); +export const AGENDA_ITEM_CREATION_ERROR = Object.freeze({ DESC: "Agenda item not created", CODE: "agendaItem.notCreated", MESSAGE: "agendaItem.notCreated", PARAM: "agendaItem", -}; -export const AGENDA_SECTION_NOT_FOUND_ERROR = { +}); +export const AGENDA_SECTION_NOT_FOUND_ERROR = Object.freeze({ DESC: "Agenda section not found", CODE: "agendaSection.notFound", MESSAGE: "agendaSection.notFound", PARAM: "agendaSection", -}; -export const NOTE_NOT_FOUND_ERROR = { +}); +export const NOTE_NOT_FOUND_ERROR = Object.freeze({ MESSAGE: "Error: Note not found", CODE: "note.notFound", PARAM: "noteValidation", -}; +}); -export const UNAUTHORIZED_REMOVE_NOTE_ERROR = { +export const UNAUTHORIZED_REMOVE_NOTE_ERROR = Object.freeze({ MESSAGE: "Error: Unauthorized to remove note", CODE: "note.unauthorizedRemove", PARAM: "noteRemovalValidation", -}; +}); -export const UNAUTHORIZED_UPDATE_NOTE_ERROR = { +export const UNAUTHORIZED_UPDATE_NOTE_ERROR = Object.freeze({ MESSAGE: "Error: Unauthorized to update note", CODE: "note.unauthorizedUpdate", PARAM: "noteUpdateValidation", -}; +}); -export const USER_NOT_FOUND_ERROR = { +export const USER_NOT_FOUND_ERROR = Object.freeze({ DESC: "User not found", CODE: "user.notFound", MESSAGE: "user.notFound", PARAM: "user", -}; -export const USER_NOT_MADE_PLEDGE_ERROR = { +}); +export const USER_NOT_MADE_PLEDGE_ERROR = Object.freeze({ DESC: "User has not made a pledge", CODE: "user.notPledged", MESSAGE: "user.notPledged", PARAM: "user", -}; -export const USER_NOT_MEMBER_FOR_ORGANIZATION = { +}); +export const USER_NOT_MEMBER_FOR_ORGANIZATION = Object.freeze({ DESC: "User is not a member of the organization", CODE: "user.notMember", MESSAGE: "user.notMember", PARAM: "user", -}; -export const USER_TO_BE_REMOVED_NOT_FOUND_ERROR = { +}); +export const USER_TO_BE_REMOVED_NOT_FOUND_ERROR = Object.freeze({ DESC: "User to be removed not found", CODE: "user.notFound", MESSAGE: "user.notFound", PARAM: "user", -}; -export const SUPERADMIN_CANT_CHANGE_OWN_ROLE = { +}); +export const SUPERADMIN_CANT_CHANGE_OWN_ROLE = Object.freeze({ MESSAGE: "Superadmin's are not allowed to change their own roles. This is done as SUPERADMIN is the highest level of access to the system, and downgrading their own role may result in them being locked out of the system.", CODE: "superadmin.NotChangeSelf", PARAM: "superadmin.NotChangeSelf", -}; -export const TRANSLATION_ALREADY_PRESENT_ERROR = { +}); +export const TRANSLATION_ALREADY_PRESENT_ERROR = Object.freeze({ DESC: "Translation Already Present", CODE: "translation.alreadyPresent", MESSAGE: "translation.alreadyPresent", PARAM: "translationAlreadyPresent", -}; -export const INVALID_CREDENTIALS_ERROR = { +}); +export const INVALID_CREDENTIALS_ERROR = Object.freeze({ DESC: "Invalid credentials", CODE: "invalid.credentials", MESSAGE: "invalid.credentials", PARAM: "credentials", -}; -export const INVALID_REFRESH_TOKEN_ERROR = { +}); +export const INVALID_REFRESH_TOKEN_ERROR = Object.freeze({ DESC: "Invalid refreshToken", CODE: "invalid.refreshToken", MESSAGE: "invalid.refreshToken", PARAM: "refreshToken", -}; +}); -export const USER_PROFILE_IMAGE_NOT_FOUND_ERROR = { +export const USER_PROFILE_IMAGE_NOT_FOUND_ERROR = Object.freeze({ DESC: "User profile image not found", CODE: "user.profileImage.notFound", MESSAGE: "user.profileImage.notFound", PARAM: "userProfileImage", -}; +}); -export const EMAIL_ALREADY_EXISTS_ERROR = { +export const EMAIL_ALREADY_EXISTS_ERROR = Object.freeze({ DESC: "Email already exists", CODE: "email.alreadyExists", MESSAGE: "email.alreadyExists", PARAM: "email", -}; +}); -export const EVENT_VOLUNTEER_NOT_FOUND_ERROR = { +export const EVENT_VOLUNTEER_NOT_FOUND_ERROR = Object.freeze({ DESC: "Volunteer not found", CODE: "eventVolunteer.notFound", MESSAGE: "eventVolunteer.notFound", PARAM: "eventVolunteers", -}; +}); -export const EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR = { +export const EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR = Object.freeze({ DESC: "Volunteer group not found", CODE: "eventVolunteerGroup.notFound", MESSAGE: "eventVolunteerGroup.notFound", PARAM: "eventVolunteerGroup", -}; +}); -export const EVENT_VOLUNTEER_INVITE_USER_MISTMATCH = { +export const EVENT_VOLUNTEER_INVITE_USER_MISTMATCH = Object.freeze({ DESC: "Current User is not the user of Event Volunteer", CODE: "eventVolunteer.userMismatch", MESSAGE: "eventVolunteer.userMismatch", PARAM: "eventVolunteers", -}; +}); -export const USER_ALREADY_CHECKED_IN = { +export const USER_ALREADY_CHECKED_IN = Object.freeze({ MESSAGE: "The user has already been checked in for this event.", CODE: "user.alreadyCheckedIn", PARAM: "user.alreadyCheckedIn", -}; +}); -export const USER_ALREADY_CHECKED_OUT = { +export const USER_ALREADY_CHECKED_OUT = Object.freeze({ MESSAGE: "The user has already been checked out for this event.", CODE: "user.alreadyCheckedOut", PARAM: "user.alreadyCheckedOut", -}; +}); -export const SAMPLE_ORGANIZATION_ALREADY_EXISTS = { +export const SAMPLE_ORGANIZATION_ALREADY_EXISTS = Object.freeze({ DESC: "Sample Organization was already generated", CODE: "sampleOrganization.duplicate", MESSAGE: "sampleOrganization.duplicate", PARAM: "sampleOrganization", -}; +}); -export const CUSTOM_DATA_NOT_FOUND = { +export const CUSTOM_DATA_NOT_FOUND = Object.freeze({ MESSAGE: "Unable to remove non-existent custom data", CODE: "customData.notFound", PARAM: "customData.notFound", -}; +}); -export const CUSTOM_FIELD_NOT_FOUND = { +export const CUSTOM_FIELD_NOT_FOUND = Object.freeze({ MESSAGE: "Unable to remove non-existent custom field", CODE: "customField.notFound", PARAM: "customField.notFound", -}; +}); -export const CUSTOM_FIELD_NAME_MISSING = { +export const CUSTOM_FIELD_NAME_MISSING = Object.freeze({ MESSAGE: "The name of the custom field is missing", CODE: "customField.isMissing", PARAM: "customField.isMissing", -}; +}); -export const CUSTOM_FIELD_TYPE_MISSING = { +export const CUSTOM_FIELD_TYPE_MISSING = Object.freeze({ MESSAGE: "The type of the custom field is missing", CODE: "customField.isMissing", PARAM: "customField.isMissing", -}; +}); -export const ATTENDEE_NOT_FOUND = { +export const ATTENDEE_NOT_FOUND = Object.freeze({ DESC: "Attendee not found", CODE: "attendee.notFound", MESSAGE: "attendee.notFound", PARAM: "attendee", -}; +}); -export const PRELOGIN_IMAGERY_FIELD_EMPTY = { +export const PRELOGIN_IMAGERY_FIELD_EMPTY = Object.freeze({ MESSAGE: "Website name, website link and the website logo cannot be empty", CODE: "preLoginImagery.empty", PARAM: "preLoginImagery.empty", -}; +}); export const MAXIMUM_FETCH_LIMIT = 100; diff --git a/src/db.ts b/src/db.ts index 374dd85f03..f445206a56 100644 --- a/src/db.ts +++ b/src/db.ts @@ -6,16 +6,19 @@ import { checkReplicaSet } from "./utilities/checkReplicaSet"; let session!: mongoose.ClientSession; export const connect = async (dbName?: string): Promise => { - // firstly we check if a connection to the database already exists. + // Check if a connection to the database already exists. if (mongoose.connection.readyState !== 0) { - // if the connection state is not 0, it means a connection already exists so we return. + // If a connection already exists, return immediately. return; } - // if no connection exists, we try to establish a new connection. + + // If no connection exists, attempt to establish a new connection. try { await mongoose.connect(MONGO_DB_URL as string, { dbName, }); + + // Check if connected to a replica set and start a session if true. const replicaSet = await checkReplicaSet(); if (replicaSet) { logger.info("Session started --> Connected to a replica set!"); @@ -27,6 +30,7 @@ export const connect = async (dbName?: string): Promise => { if (error instanceof Error) { const errorMessage = error.toString(); if (errorMessage.includes("ECONNREFUSED")) { + // Handle connection errors due to common issues. logger.error("\n\n\n\x1b[1m\x1b[31m%s\x1b[0m", error); logger.error( "\n\n\x1b[1m\x1b[34m%s\x1b[0m", diff --git a/src/directives/directiveTransformer/authDirectiveTransformer.ts b/src/directives/directiveTransformer/authDirectiveTransformer.ts index 8863b84e80..630a00fa86 100644 --- a/src/directives/directiveTransformer/authDirectiveTransformer.ts +++ b/src/directives/directiveTransformer/authDirectiveTransformer.ts @@ -3,6 +3,22 @@ import { defaultFieldResolver } from "graphql"; import type { GraphQLSchema } from "graphql/type/schema"; import { errors, requestContext } from "../../libraries"; +/** + * A function to transform a GraphQL schema by adding authentication logic + * to the fields with the specified directive. + * + * @param schema - The original GraphQL schema to be transformed. + * @param directiveName - The name of the directive that will trigger the transformation. + * + * @see Parent File: + * - `src/index.ts` + * + * @returns A new GraphQL schema with the authentication logic applied. + * + * @example + * `const transformedSchema = authDirectiveTransformer(originalSchema, 'auth');` + */ + function authDirectiveTransformer( schema: GraphQLSchema, directiveName: string, @@ -15,18 +31,24 @@ function authDirectiveTransformer( fieldConfig, directiveName, )?.[0]; + if (authDirective) { const { resolve = defaultFieldResolver } = fieldConfig; fieldConfig.resolve = (root, args, context, info): string => { - if (context.expired || !context.isAuth) + // Check if the user is authenticated and the session is not expired + if (context.expired || !context.isAuth) { throw new errors.UnauthenticatedError( requestContext.translate("user.notAuthenticated"), "user.notAuthenticated --auth directive", "userAuthentication", ); + } + + // Call the original resolver with the context return resolve(root, args, context, info) as string; }; + return fieldConfig; } }, diff --git a/src/directives/directiveTransformer/roleDirectiveTransformer.ts b/src/directives/directiveTransformer/roleDirectiveTransformer.ts index 6abc8a3746..70201ed819 100644 --- a/src/directives/directiveTransformer/roleDirectiveTransformer.ts +++ b/src/directives/directiveTransformer/roleDirectiveTransformer.ts @@ -5,6 +5,22 @@ import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors } from "../../libraries"; import { User } from "../../models"; +/** + * A function to transform a GraphQL schema by adding role-based authorization + * logic to the fields with the specified directive. + * + * @param schema - The original GraphQL schema to be transformed. + * @param directiveName - The name of the directive that will trigger the transformation. + * + * @see Parent File: + * - `src/index.ts` + * + * @returns A new GraphQL schema with the role-based authorization logic applied. + * + * @example + * const transformedSchema = roleDirectiveTransformer(originalSchema, 'role'); + */ + function roleDirectiveTransformer( schema: GraphQLSchema, directiveName: string, @@ -29,10 +45,12 @@ function roleDirectiveTransformer( context, info, ): Promise => { + // Fetch the current user from the database using the userId from the context const currentUser = await User.findOne({ _id: context.userId, }).lean(); + // If no user is found, throw a "Not Found" error if (!currentUser) { throw new errors.NotFoundError( USER_NOT_FOUND_ERROR.MESSAGE, @@ -49,8 +67,10 @@ function roleDirectiveTransformer( // ); // } + // Add the current user to the context for use in the resolver context.user = currentUser; + // Call the original resolver with the updated context return resolve(root, args, context, info) as string; }; diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts index cdf5038915..cd42ef51b4 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -10,18 +10,26 @@ import { } from "../recurringEventHelpers"; /** - * This function creates the instances of a recurring event upto a certain date. - * @param args - payload of the createEvent mutation - * @param creatorId - _id of the creator - * @param organizationId - _id of the organization the events belongs to - * @remarks The following steps are followed: - * 1. Create a default recurrenceRuleData. - * 2. Generate a recurrence rule string based on the recurrenceRuleData. - * 3. Create a baseRecurringEvent on which recurring instances would be based. - * 4. Get the dates for recurring instances. - * 5. Create a recurrenceRule document. - * 6. Generate recurring instances according to the recurrence rule. - * @returns Created recurring event instance + * Creates instances of a recurring event up to a specified end date. + * + * @param args - The payload of the createEvent mutation, including event data and recurrence rule. + * @param creatorId - The ID of the event creator. + * @param organizationId - The ID of the organization to which the event belongs. + * @param session - The MongoDB client session for transactional operations. + * @returns The created instance of the recurring event. + * + * @see Parent file: + * - `resolvers/Mutation/createEvent.ts` + * - `resolvers/Query/eventsByOrganizationConnection.ts` + * + * @remarks + * Steps performed by this function: + * 1. If no recurrence rule is provided, defaults to weekly recurrence starting from a given date. + * 2. Generates a recurrence rule string based on provided or default recurrence data. + * 3. Creates a base recurring event template in the database. + * 4. Retrieves dates for all recurring instances based on the recurrence rule. + * 5. Saves the recurrence rule in the database for future reference. + * 6. Generates and saves instances of recurring events based on the recurrence rule. */ export const createRecurringEvent = async ( @@ -30,11 +38,12 @@ export const createRecurringEvent = async ( organizationId: string, session: mongoose.ClientSession, ): Promise => { + // Extract event data and recurrence rule information from arguments const { data } = args; let { recurrenceRuleData } = args; + // Set a default weekly recurrence rule if none is provided if (!recurrenceRuleData) { - // create a default recurrence rule -> infinite weekly recurrence recurrenceRuleData = { frequency: "WEEKLY", recurrenceStartDate: data.startDate, @@ -44,16 +53,15 @@ export const createRecurringEvent = async ( const { recurrenceStartDate, recurrenceEndDate } = recurrenceRuleData; - // generate a recurrence rule string which would be used to generate rrule object - // and get recurrence dates + // 1. Generate a string representation of the recurrence rule const recurrenceRuleString = generateRecurrenceRuleString(recurrenceRuleData); - // create a base recurring event first, based on which all the + // 2.create a base recurring event first, based on which all the // recurring instances would be dynamically generated const baseRecurringEvent = await Event.create( [ { - ...data, + ...data, // Spread event data from original arguments recurring: true, startDate: recurrenceStartDate, endDate: recurrenceEndDate, @@ -63,10 +71,10 @@ export const createRecurringEvent = async ( organization: organizationId, }, ], - { session }, + { session }, // Use the provided session if available ); - // get the dates for the recurringInstances, and the date of the last instance + // 3. get the dates for the recurringInstances, and the date of the last instance // to be generated in this operation (rest would be generated dynamically during query) const recurringInstanceDates = getRecurringInstanceDates( recurrenceRuleString, @@ -74,26 +82,27 @@ export const createRecurringEvent = async ( recurrenceEndDate, ); - // get the date for the latest created instance + // 4. Extract the date of the last created instance const latestInstanceDate = recurringInstanceDates[recurringInstanceDates.length - 1]; - // create a recurrenceRule document that would contain the recurrence pattern + + // 5. Create a separate document to store the recurrence pattern details const recurrenceRule = await createRecurrenceRule( recurrenceRuleString, recurrenceStartDate, recurrenceEndDate, organizationId, - baseRecurringEvent[0]?._id.toString(), + baseRecurringEvent[0]?._id.toString(), // Get ID of the base event latestInstanceDate, session, ); - // generate the recurring instances and get an instance back + // 6. Generate all the recurring event instances based on the rule and dates const recurringEventInstance = await generateRecurringEventInstances({ - data, - baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), - recurrenceRuleId: recurrenceRule?._id.toString(), - recurringInstanceDates, + data, // Event data for all instances + baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), // Base event ID + recurrenceRuleId: recurrenceRule?._id.toString(), // Recurrence rule ID + recurringInstanceDates, // Array of dates for each instance creatorId, organizationId, session, diff --git a/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts b/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts index 1165f5f99c..130bec140e 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts @@ -10,18 +10,25 @@ import type { InterfaceRecurringEvent } from "../recurringEventHelpers/generateR import { RECURRING_EVENT_INSTANCES_QUERY_LIMIT } from "../../../constants"; /** - * This function creates the instances of a recurring event upto a certain date during queries. - * @param organizationId - _id of the organization the events belong to - * @remarks The following steps are followed: - * 1. Get the limit date upto which we would want to query the recurrenceRules and generate new instances. - * 2. Get the recurrence rules to be used for instance generation during this query. - * 3. For every recurrence rule found: - * - find the base recurring event to get the data to be used for new instance generation. - * - get the number of existing instances and how many more to generate based on the recurrenceRule's count (if specified). - * - generate new instances after their latestInstanceDates. - * - update the latestInstanceDate. + * Creates instances of recurring events up to a specified date during queries. + * + * @param organizationId - The ID of the organization to which the events belong. + * + * @see Parent file: + * - `resolvers/Mutation/createEvent.ts.` + * - `resolvers/Query/eventsByOrganizationConnection.ts.` + * + * @remarks + * This function follows these steps: + * 1. Calculates the date limit up to which recurrence rules are queried and new instances are generated. + * 2. Retrieves recurrence rules based on the organization ID and their latest instance dates. + * 3. For each recurrence rule found: + * - Finds the base recurring event to gather data for new instance generation. + * - Determines how many existing instances exist and calculates how many new instances to generate. + * - Generates new instances starting from the latest instance date recorded. + * - Updates the latest instance date for the recurrence rule. + * */ - export const createRecurringEventInstancesDuringQuery = async ( organizationId: string | undefined | null, ): Promise => { @@ -29,14 +36,14 @@ export const createRecurringEventInstancesDuringQuery = async ( return; } - // get the current calendar date in UTC midnight + // Get the current UTC date at midnight const calendarDate = convertToUTCDate(new Date()); const queryUptoDate = addYears( calendarDate, RECURRING_EVENT_INSTANCES_QUERY_LIMIT, ); - // get the recurrenceRules + // Retrieve recurrence rules that require new instances const recurrenceRules = await RecurrenceRule.find({ organizationId, latestInstanceDate: { $lt: queryUptoDate }, @@ -44,21 +51,21 @@ export const createRecurringEventInstancesDuringQuery = async ( await Promise.all( recurrenceRules.map(async (recurrenceRule) => { - // find the baseRecurringEvent for the recurrenceRule + // Find the base recurring event associated with the recurrence rule const baseRecurringEvent = await Event.find({ _id: recurrenceRule.baseRecurringEventId, }).lean(); - // get the data from the baseRecurringEvent + // Extract necessary data from the base recurring event const { _id: baseRecurringEventId, ...data } = baseRecurringEvent[0]; - // get the input data for the generateRecurringEventInstances function + // Prepare input data for generating recurring event instances const currentInputData: InterfaceRecurringEvent = { ...data, organizationId: recurrenceRule.organizationId.toString(), }; - // get the properties from recurrenceRule + // Extract properties from the recurrence rule const { _id: recurrenceRuleId, recurrenceEndDate, @@ -67,10 +74,10 @@ export const createRecurringEventInstancesDuringQuery = async ( count: totalInstancesCount, } = recurrenceRule; - // get the date from which new instances would be generated + // Determine the start date for generating new instances const currentRecurrenceStartDate = addDays(latestInstanceDate, 1); - // get the dates for recurrence + // Calculate dates for new recurring instances let recurringInstanceDates = getRecurringInstanceDates( recurrenceRuleString, currentRecurrenceStartDate, @@ -78,7 +85,7 @@ export const createRecurringEventInstancesDuringQuery = async ( queryUptoDate, ); - // find out how many instances following the recurrence rule already exist and how many more to generate + // Adjust the number of instances to generate based on specified count if (totalInstancesCount) { const totalExistingInstances = await Event.countDocuments({ recurrenceRuleId, @@ -92,19 +99,18 @@ export const createRecurringEventInstancesDuringQuery = async ( ); } - /* c8 ignore start */ + // Start a transaction if a session is available if (session) { - // start a transaction session.startTransaction(); } - /* c8 ignore stop */ try { + // Generate new instances if dates are available if (recurringInstanceDates && recurringInstanceDates.length) { const updatedLatestRecurringInstanceDate = recurringInstanceDates[recurringInstanceDates.length - 1]; - // update the latestInstanceDate of the recurrenceRule + // Update the latest instance date for the recurrence rule await RecurrenceRule.updateOne( { _id: recurrenceRuleId, @@ -115,7 +121,7 @@ export const createRecurringEventInstancesDuringQuery = async ( { session }, ); - // generate recurring event instances + // Generate recurring event instances await generateRecurringEventInstances({ data: currentInputData, baseRecurringEventId: baseRecurringEventId.toString(), @@ -127,21 +133,17 @@ export const createRecurringEventInstancesDuringQuery = async ( }); } - /* c8 ignore start */ + // Commit the transaction if everything is successful if (session) { - // commit transaction if everything's successful await session.commitTransaction(); } } catch (error) { + // Abort the transaction if an error occurs if (session) { - // abort transaction if something fails await session.abortTransaction(); } - throw error; } - - /* c8 ignore stop */ }), ); }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 80ac77e0a9..b4e8cb9316 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -5,24 +5,34 @@ import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTyp import { cacheEvents } from "../../../services/EventCache/cacheEvents"; /** - * This function generates a single non-recurring event. - * @param args - the arguments provided for the createEvent mutation. - * @param creatorId - _id of the current user. - * @param organizationId - _id of the current organization. - * @remarks The following steps are followed: - * 1. Create an event document. - * 2. Associate the event with the user - * 3. Cache the event. - * @returns The created event. + * Creates a single non-recurring event. + * + * @param args - Arguments provided for the createEvent mutation, including event data. + * @param creatorId - The ID of the current user creating the event. + * @param organizationId - The ID of the organization to which the event belongs. + * @param session - The MongoDB client session for transactional operations. + * + * @see Parent file: + * - `resolvers/Mutation/createEvent.ts`, + * - `resolvers/Query/eventsByOrganizationConnection.ts` + * + * @remarks + * This function follows these steps: + * 1. Creates an event document in the database with provided data. + * 2. Associates the event with the current user as creator and admin. + * 3. Updates user's registered events list with the new event. + * 4. Updates user's AppUserProfile with event admin and created events references. + * 5. Caches the newly created event for faster access. + * + * @returns The created event instance. */ - export const createSingleEvent = async ( args: MutationCreateEventArgs, creatorId: string, organizationId: string, session: mongoose.ClientSession, ): Promise => { - // create the single event + // Create the single event in the database const createdEvent = await Event.create( [ { @@ -35,7 +45,7 @@ export const createSingleEvent = async ( { session }, ); - // associate event with the user + // Associate the event with the user await EventAttendee.create( [ { @@ -45,6 +55,8 @@ export const createSingleEvent = async ( ], { session }, ); + + // Update the user's registered events list await User.updateOne( { _id: creatorId, @@ -56,6 +68,8 @@ export const createSingleEvent = async ( }, { session }, ); + + // Update the user's AppUserProfile with event admin and created events references await AppUserProfile.updateOne( { userId: creatorId, @@ -69,8 +83,9 @@ export const createSingleEvent = async ( { session }, ); - // cache the event + // Cache the event for faster access await cacheEvents([createdEvent[0]]); + // Return the created event instance return createdEvent[0]; }; diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index b05861a980..c85af953d4 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,3 +1,18 @@ +/** + * Exported function that creates a single non-recurring event. + * + * @see createSingleEvent + */ export { createSingleEvent } from "./createSingleEvent"; + +/** + * Exported function that creates instances of a recurring event. + * @see createRecurringEvent + */ export { createRecurringEvent } from "./createRecurringEvent"; + +/** + * Exported function that generates instances of recurring events up to a specified date during queries. + * @see createRecurringEventInstancesDuringQuery + */ export { createRecurringEventInstancesDuringQuery } from "./createRecurringEventInstancesDuringQuery"; diff --git a/src/helpers/event/deleteEventHelpers/deleteRecurringEvent.ts b/src/helpers/event/deleteEventHelpers/deleteRecurringEvent.ts index 9545d64cce..810164a525 100644 --- a/src/helpers/event/deleteEventHelpers/deleteRecurringEvent.ts +++ b/src/helpers/event/deleteEventHelpers/deleteRecurringEvent.ts @@ -10,26 +10,33 @@ import { } from "../../../constants"; /** - * This function deletes thisInstance / allInstances / thisAndFollowingInstances of a recurring event. - * @param args - removeEventArgs - * @param event - an instance of the recurring event to be deleted. - * @remarks The following steps are followed: - * 1. get the recurrence rule and the base recurring event. - * 2. if the instance is an exception instance or if we're deleting thisInstance only, just delete that single instance. - * 3. if it's a bulk delete operation, handle it accordingly. + * Deletes instances of a recurring event based on the delete type specified. + * Delete types include: thisInstance, allInstances, thisAndFollowingInstances. + * + * @param args - Arguments containing details for the event deletion. + * @param event - The instance of the recurring event to be deleted. + * @param session - The MongoDB client session for transactional operations. + * + * @remarks + * This function follows these steps: + * 1. Retrieves the recurrence rule associated with the event. + * 2. Retrieves the base recurring event to which the event belongs. + * 3. If the event is an exception instance or deleting a single instance (`thisInstance`), deletes that specific instance. + * 4. If deleting all instances (`allInstances`), deletes all instances associated with the recurrence rule. + * 5. If deleting this and following instances (`thisAndFollowingInstances`), deletes all instances starting from the specified event instance. + * */ - export const deleteRecurringEvent = async ( args: MutationRemoveEventArgs, event: InterfaceEvent, session: mongoose.ClientSession, ): Promise => { - // get the recurrenceRule + // Retrieve the recurrence rule associated with the event const recurrenceRule = await RecurrenceRule.findOne({ _id: event.recurrenceRuleId, }); - // throws error if the recurrence rule doesn't exist + // Throw error if the recurrence rule doesn't exist if (recurrenceRule === null) { throw new errors.NotFoundError( requestContext.translate(RECURRENCE_RULE_NOT_FOUND.MESSAGE), @@ -38,12 +45,12 @@ export const deleteRecurringEvent = async ( ); } - // get the baseRecurringEvent + // Retrieve the base recurring event associated with the event const baseRecurringEvent = await Event.findOne({ _id: event.baseRecurringEventId, }); - // throws error if the base recurring event doesn't exist + // Throw error if the base recurring event doesn't exist if (baseRecurringEvent === null) { throw new errors.NotFoundError( requestContext.translate(BASE_RECURRING_EVENT_NOT_FOUND.MESSAGE), @@ -52,12 +59,12 @@ export const deleteRecurringEvent = async ( ); } + // Determine the type of deletion operation based on args.recurringEventDeleteType if ( event.isRecurringEventException || args.recurringEventDeleteType === "thisInstance" ) { - // if the event is an exception or if it's deleting thisInstance only, - // just delete this single instance + // If the event is an exception or deleting thisInstance, delete the single event instance await deleteSingleEvent( event._id.toString(), session, @@ -65,19 +72,17 @@ export const deleteRecurringEvent = async ( baseRecurringEvent._id.toString(), ); } else if (args.recurringEventDeleteType === "allInstances") { - // delete all the instances - // and update the recurrenceRule and baseRecurringEvent accordingly + // If deleting allInstances, delete all instances associated with the recurrence rule await deleteRecurringEventInstances( - null, // because we're going to delete all the instances, which we could get from the recurrence rule + null, // Passing null as we delete all instances controlled by the recurrence rule recurrenceRule, baseRecurringEvent, session, ); } else { - // delete this and following the instances - // and update the recurrenceRule and baseRecurringEvent accordingly + // If deleting thisAndFollowingInstances, delete this and all following instances await deleteRecurringEventInstances( - event, // we'll find all the instances after(and including) this one and delete them + event, // Delete all instances from this instance onwards recurrenceRule, baseRecurringEvent, session, diff --git a/src/helpers/event/deleteEventHelpers/deleteRecurringEventInstances.ts b/src/helpers/event/deleteEventHelpers/deleteRecurringEventInstances.ts index 1928a4b45e..385164f9bc 100644 --- a/src/helpers/event/deleteEventHelpers/deleteRecurringEventInstances.ts +++ b/src/helpers/event/deleteEventHelpers/deleteRecurringEventInstances.ts @@ -13,29 +13,31 @@ import { shouldUpdateBaseRecurringEvent } from "../updateEventHelpers"; import { removeDanglingDocuments } from "../recurringEventHelpers"; /** - * This function deletes allInstances / thisAndFollowingInstances of a recurring event. - * @param event - the event to be deleted: - * - in case of deleting thisAndFollowingInstances, it would represent this instance. - * - in case of deleting allInstances, it would be null. - * @param recurrenceRule - the recurrence rule followed by the instances. - * @param baseRecurringEvent - the base recurring event. - * @remarks The following steps are followed: - * 1. get the instances to be deleted. - * 2. remove the associations of the instances. - * 3. delete the instances. - * 4. update the recurrenceRule and baseRecurringEvent accordingly. - * 5. remove any dangling recurrence rule and base recurring event documents. + * Deletes all instances or thisAndFollowingInstances of a recurring event. + * + * @param event - The event instance to be deleted: + * - For thisAndFollowingInstances, represents the starting instance. + * - For allInstances, should be null. + * @param recurrenceRule - The recurrence rule associated with the instances. + * @param baseRecurringEvent - The base recurring event from which instances are derived. + * + * @remarks + * This function performs the following steps: + * 1. Constructs a query object to fetch instances based on the delete type. + * 2. Retrieves and deletes all associated documents (attendees, users, profiles, action items). + * 3. Deletes the instances themselves. + * 4. Updates the recurrence rule and base recurring event as needed. + * 5. Removes any dangling documents related to the recurrence rule and base recurring event. */ - export const deleteRecurringEventInstances = async ( event: InterfaceEvent | null, recurrenceRule: InterfaceRecurrenceRule, baseRecurringEvent: InterfaceEvent, session: mongoose.ClientSession, ): Promise => { - // get the query object to filter events to be deleted: - // - if we're deleting thisAndFollowingInstance, it will find all the instances after(and including) this one - // - if we're deleting allInstances, it will find all the instances + // Construct the query object to filter events to be deleted: + // - For thisAndFollowingInstances, find all instances after (and including) this one + // - For allInstances, find all instances const eventsQueryObject: { recurrenceRuleId: Types.ObjectId; baseRecurringEventId: Types.ObjectId; @@ -53,7 +55,7 @@ export const deleteRecurringEventInstances = async ( eventsQueryObject.startDate = { $gte: event.startDate }; } - // get all the instances to be deleted + // Get all the instances to be deleted const recurringEventInstances = await Event.find( { ...eventsQueryObject, @@ -62,12 +64,12 @@ export const deleteRecurringEventInstances = async ( { session }, ); - // get the ids of those instances + // Get the IDs of those instances const recurringEventInstancesIds = recurringEventInstances.map( (recurringEventInstance) => recurringEventInstance._id, ); - // remove all the associations for the instances that are deleted + // Remove all associations for the instances that are being deleted await Promise.all([ EventAttendee.deleteMany( { eventId: { $in: recurringEventInstancesIds } }, @@ -102,13 +104,13 @@ export const deleteRecurringEventInstances = async ( { session }, ), - // delete action items associated to the instances + // Delete action items associated with the instances ActionItem.deleteMany( { eventId: { $in: recurringEventInstancesIds } }, { session }, ), - // delete the instances + // Delete the instances themselves Event.deleteMany( { _id: { $in: recurringEventInstancesIds }, @@ -119,7 +121,7 @@ export const deleteRecurringEventInstances = async ( ), ]); - // get the instances following the current recurrence rule (if any) + // Check if there are instances following the current recurrence rule const instancesFollowingCurrentRecurrence = await Event.find( { recurrenceRuleId: recurrenceRule._id, @@ -129,18 +131,18 @@ export const deleteRecurringEventInstances = async ( { session }, ).sort({ startDate: -1 }); - // check if more instances following this recurrence rule still exist + // Determine if more instances following this recurrence rule exist const moreInstancesExist = instancesFollowingCurrentRecurrence && instancesFollowingCurrentRecurrence.length; if (moreInstancesExist) { - // get the latest instance following the old recurrence rule + // Get the latest instance following the current recurrence rule const updatedEndDateString = instancesFollowingCurrentRecurrence[0].startDate; const updatedEndDate = new Date(updatedEndDateString); - // update the latestInstanceDate and recurrenceEndDate of the current recurrenceRule + // Update the latestInstanceDate and recurrenceEndDate of the current recurrenceRule await RecurrenceRule.findOneAndUpdate( { _id: recurrenceRule._id, @@ -152,7 +154,7 @@ export const deleteRecurringEventInstances = async ( { session }, ).lean(); - // update the baseRecurringEvent if it is the latest recurrence rule that the instances were following + // Update the baseRecurringEvent if it is the latest recurrence rule that the instances were following if ( shouldUpdateBaseRecurringEvent( recurrenceRule.recurrenceEndDate?.toString(), @@ -172,8 +174,8 @@ export const deleteRecurringEventInstances = async ( ); } } else { - // if no instances conforming to the current recurrence rule exist - // find any previous recurrence rules that were associated with this baseRecurringEvent + // If no instances conforming to the current recurrence rule exist, + // find any previous recurrence rules associated with this baseRecurringEvent const previousRecurrenceRules = await RecurrenceRule.find( { baseRecurringEventId: baseRecurringEvent._id, @@ -188,7 +190,7 @@ export const deleteRecurringEventInstances = async ( const previousRecurrenceRulesExist = previousRecurrenceRules && previousRecurrenceRules.length; if (previousRecurrenceRulesExist) { - // update the baseRecurringEvent if it is the latest recurrence rule that the instances were following + // Update the baseRecurringEvent if it is the latest recurrence rule that the instances were following if ( shouldUpdateBaseRecurringEvent( recurrenceRule.recurrenceEndDate?.toString(), @@ -208,7 +210,7 @@ export const deleteRecurringEventInstances = async ( } } - // remove any dangling recurrence rule and base recurring event documents + // Remove any dangling recurrence rule and base recurring event documents await removeDanglingDocuments( recurrenceRule._id.toString(), baseRecurringEvent._id.toString(), diff --git a/src/helpers/event/deleteEventHelpers/deleteSingleEvent.ts b/src/helpers/event/deleteEventHelpers/deleteSingleEvent.ts index ee15de8122..8ad1a39a7f 100644 --- a/src/helpers/event/deleteEventHelpers/deleteSingleEvent.ts +++ b/src/helpers/event/deleteEventHelpers/deleteSingleEvent.ts @@ -9,38 +9,33 @@ import { import { removeDanglingDocuments } from "../recurringEventHelpers"; /** - * This function deletes a single event. - * @param event - the event to be deleted: - * @remarks The following steps are followed: - * 1. remove the associations of the event. - * 2. delete the event. + * Deletes a single event. + * + * @param eventId - The ID of the event to be deleted. + * @param session - The MongoDB client session for transactional operations. + * @param recurrenceRule - Optional ID of the recurrence rule associated with the event (for recurring events). + * @param baseRecurringEvent - Optional ID of the base recurring event (for recurring events). + * + * @remarks + * This function performs the following steps: + * 1. Removes all associations (attendees, users, profiles, action items) related to the event. + * 2. Deletes the event document itself. + * 3. If provided, removes any dangling documents related to the recurrence rule and base recurring event. */ - export const deleteSingleEvent = async ( eventId: string, session: mongoose.ClientSession, recurrenceRule?: string, baseRecurringEvent?: string, ): Promise => { - // remove the associations of the current event + // Remove associations of the current event await Promise.all([ - EventAttendee.deleteMany( - { - eventId, - }, - { session }, - ), - + EventAttendee.deleteMany({ eventId }, { session }), User.updateMany( { registeredEvents: eventId }, - { - $pull: { - registeredEvents: eventId, - }, - }, + { $pull: { registeredEvents: eventId } }, { session }, ), - AppUserProfile.updateMany( { $or: [{ createdEvents: eventId }, { eventAdmin: eventId }], @@ -53,22 +48,12 @@ export const deleteSingleEvent = async ( }, { session }, ), - ActionItem.deleteMany({ eventId }, { session }), - - Event.deleteOne( - { - _id: eventId, - }, - { - session, - }, - ), + Event.deleteOne({ _id: eventId }, { session }), ]); + // If deleting a recurring event, remove any dangling recurrence rule and base recurring event documents if (recurrenceRule && baseRecurringEvent) { - // they would exist while we're deleting a recurring event - // remove any dangling recurrence rule and base recurring event documents await removeDanglingDocuments(recurrenceRule, baseRecurringEvent, session); } }; diff --git a/src/helpers/event/deleteEventHelpers/index.ts b/src/helpers/event/deleteEventHelpers/index.ts index 5965afbb0f..39bbaedbf5 100644 --- a/src/helpers/event/deleteEventHelpers/index.ts +++ b/src/helpers/event/deleteEventHelpers/index.ts @@ -1,3 +1,17 @@ +/** + * Exported function that deletes a single event. + * @see {@link deleteSingleEvent} for more details. + */ export { deleteSingleEvent } from "./deleteSingleEvent"; + +/** + * Exported function that deletes a recurring event or its instances. + * @see {@link deleteRecurringEvent} for more details. + */ export { deleteRecurringEvent } from "./deleteRecurringEvent"; + +/** + * Exported function that deletes all instances or this-and-following instances of a recurring event. + * @see {@link deleteRecurringEventInstances} for more details. + */ export { deleteRecurringEventInstances } from "./deleteRecurringEventInstances"; diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index 0170071776..68e1703993 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -8,20 +8,21 @@ import { } from "../../../constants"; /** - * This function generates the recurrenceRule document. - * @param recurrenceRuleString - the rrule string containing the rules that the instances would follow. - * @param recurrenceStartDate - start date of recurrence. - * @param recurrenceEndDate - end date of recurrence. - * @param organizationId - _id of the current organization. - * @param baseRecurringEventId - _id of the base recurring event. - * @param latestInstanceDate - start date of the last instance generated during this operation. - * @remarks The following steps are followed: - * 1. Create an rrule object from the rrule string. - * 2. Get the fields for the RecurrenceRule document. - * 3. Create the RecurrenceRuleDocument. - * @returns The recurrence rule document. + * Creates a recurrence rule document based on the provided parameters. + * @param recurrenceRuleString - The string representation of the recurrence rule (RRULE). + * @param recurrenceStartDate - The start date of recurrence. + * @param recurrenceEndDate - The end date of recurrence, if specified. + * @param organizationId - The unique identifier of the organization to which the recurrence rule belongs. + * @param baseRecurringEventId - The ID of the base recurring event this rule is associated with. + * @param latestInstanceDate - The start date of the last instance generated during this operation. + * @param session - The MongoDB client session for transactional operations. + * @remarks + * This function performs the following steps: + * 1. Parses the recurrenceRuleString into an rrule object using rrule string. + * 2. Extracts relevant fields from the rrule object such as frequency, weekdays, interval, etc. + * 3. Creates a new RecurrenceRule document in the database with the extracted fields. + * @returns The created recurrence rule document. */ - export const createRecurrenceRule = async ( recurrenceRuleString: string, recurrenceStartDate: Date, @@ -31,13 +32,17 @@ export const createRecurrenceRule = async ( latestInstanceDate: Date, session: mongoose.ClientSession, ): Promise => { + // Parse the recurrenceRuleString into an rrule object const recurrenceRuleObject = rrulestr(recurrenceRuleString); + // Extract necessary fields from the rrule object const { freq, byweekday, interval, count, bysetpos } = recurrenceRuleObject.options; + // Map rrule frequency to human-readable string const frequency = RECURRENCE_FREQUENCIES[freq]; + // Map rrule weekdays to human-readable strings const weekDays: string[] = []; if (byweekday) { for (const weekday of byweekday) { @@ -45,11 +50,13 @@ export const createRecurrenceRule = async ( } } + // Extract the week day occurrence in month if available let weekDayOccurenceInMonth = undefined; if (bysetpos?.length) { weekDayOccurenceInMonth = bysetpos[0]; } + // Create the RecurrenceRule document in the database const recurrenceRule = await RecurrenceRule.create( [ { @@ -69,5 +76,6 @@ export const createRecurrenceRule = async ( { session }, ); + // Return the created recurrence rule document return recurrenceRule[0].toObject(); }; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index fad7ab902e..76237320ae 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -3,20 +3,19 @@ import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; import { convertToRRuleDateString } from "../../../utilities/recurrenceDatesUtil"; /** - * This function generates the recurrence rule (rrule) string. - * @param recurrenceRuleData - the recurrenceRuleInput provided in the args. - * @param recurrenceStartDate - start date of recurrence. - * @param recurrenceEndDate - end date of recurrence. - * @remarks The following steps are followed: - * 1. Get the date strings for start and end of recurrence. - * 2. Get the recurrence rules and make a recurrenceRuleString. - * @returns The recurrence rule string that would be used to create a valid rrule object. + * Generates a recurrence rule (RRULE) string based on the provided recurrence rule input. + * @param recurrenceRuleData - The input data defining the recurrence rule. + * @returns The generated recurrence rule string suitable for creating a valid RRULE object. + * @remarks + * This function performs the following steps: + * 1. Extracts relevant fields from the recurrenceRuleData such as start date, end date, frequency, weekdays, interval, count, and week day occurrence in month. + * 2. Converts start and end dates to string format suitable for RRULE properties. + * 3. Constructs the RRULE string based on the extracted fields, using standard RRULE syntax. */ - export const generateRecurrenceRuleString = ( recurrenceRuleData: RecurrenceRuleInput, ): string => { - // destructure the recurrence rules + // Destructure the recurrence rule data const { recurrenceStartDate, recurrenceEndDate, @@ -27,34 +26,35 @@ export const generateRecurrenceRuleString = ( weekDayOccurenceInMonth, } = recurrenceRuleData; - // get the start date string for rrule's "DTSTART" property + // Convert the start date to a string formatted for RRULE "DTSTART" property const recurrenceStartDateString = convertToRRuleDateString(recurrenceStartDate); - // get the end date string for rrule's "UNTIL" property + // Convert the end date to a string formatted for RRULE "UNTIL" property, if provided let recurrenceEndDateString = ""; if (recurrenceEndDate) { recurrenceEndDateString = convertToRRuleDateString(recurrenceEndDate); } - // get weekdays for recurrence rule string, i.e. "MO", "TU", etc. + // Map weekdays to their RRULE representation (e.g., "MO" for Monday) const recurrenceWeekDays = weekDays?.map((weekDay) => { if (weekDay) { return RECURRENCE_WEEKDAYS_MAPPING[weekDay]; } }); - // sort the weekDays array + // Sort the weekDays array for consistent RRULE output recurrenceWeekDays?.sort(); - // string representing the days of the week the event would recur + // String representing the days of the week the event would recur const weekDaysString = recurrenceWeekDays?.length ? recurrenceWeekDays.join(",") : ""; - // initiate recurrence rule string + // Initialize the recurrence rule string with mandatory "DTSTART" and "FREQ" properties let recurrenceRuleString = `DTSTART:${recurrenceStartDateString}\nRRULE:FREQ=${frequency}`; + // Add optional RRULE properties based on the presence of corresponding data if (recurrenceEndDateString) { recurrenceRuleString += `;UNTIL=${recurrenceEndDateString}`; } diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index 4c5989e460..d73d617fbd 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -9,32 +9,30 @@ import { } from "../../../constants"; /** - * This function generates the dates of recurrence for the recurring event. - * @param recurrenceRuleString - the rrule string for the recurrenceRule. - * @param recurrenceStartDate - the starting date from which we want to generate instances. - * @param eventEndDate - the end date of the event - * @param queryUptoDate - the limit date to query recurrenceRules (To be used for dynamic instance generation during queries). - * @remarks The following steps are followed: - * 1. Get the date limit for instance generation based on its recurrence frequency. - * 2. Get the dates for recurring event instances. + * Generates dates of recurrence for the recurring event based on provided recurrence rules. + * @param recurrenceRuleString - The rrule string defining the recurrence rules. + * @param recurrenceStartDate - The starting date from which to generate instances. + * @param recurrenceEndDate - The end date of the event. + * @param queryUptoDate - The limit date for querying recurrence rules (used for dynamic instance generation during queries). + * @remarks + * This function performs the following steps: + * 1. Determines the date limit for instance generation based on the recurrence frequency. + * 2. Retrieves dates for recurring event instances within the specified limits. * @returns Dates for recurring instances to be generated during this operation. */ - export function getRecurringInstanceDates( recurrenceRuleString: string, recurrenceStartDate: Date, recurrenceEndDate: Date | null, queryUptoDate: Date = recurrenceStartDate, ): Date[] { - // get the rrule object + // Parse the rrule string to get the rrule object const recurrenceRuleObject: RRule = rrulestr(recurrenceRuleString); - // get the recurrence frequency + // Get the recurrence frequency from the rrule options const { freq: recurrenceFrequency } = recurrenceRuleObject.options; - // set limitEndDate according to the recurrence frequency - // and queryUptoDate, which would default to recurrenceStartDate during createRecurringEvent mutation - // and have a specific value during queries + // Determine the limit end date based on recurrence frequency let limitEndDate = addYears( queryUptoDate, RECURRING_EVENT_INSTANCES_DAILY_LIMIT, @@ -57,19 +55,19 @@ export function getRecurringInstanceDates( ); } - // if the event has no endDate + // If the event has no specified end date, use the limit end date recurrenceEndDate = recurrenceEndDate || limitEndDate; - // the date upto which we would generate the instances in this operation + // Determine the date up to which we will generate instances in this operation const generateUptoDate = new Date( Math.min(recurrenceEndDate.getTime(), limitEndDate.getTime()), ); - // get the dates of recurrence + // Retrieve the dates of recurrence based on the rrule and limits const recurringInstanceDates = recurrenceRuleObject.between( recurrenceStartDate, generateUptoDate, - true, + true, // Inclusive of start date ); return recurringInstanceDates; diff --git a/src/helpers/event/recurringEventHelpers/index.ts b/src/helpers/event/recurringEventHelpers/index.ts index 7514444f45..544604bcfe 100644 --- a/src/helpers/event/recurringEventHelpers/index.ts +++ b/src/helpers/event/recurringEventHelpers/index.ts @@ -1,5 +1,14 @@ +// Export the function that generates a recurrence rule string based on input data. export { generateRecurrenceRuleString } from "./generateRecurrenceRuleString"; + +// Export the function that calculates dates for recurring instances based on recurrence rules. export { getRecurringInstanceDates } from "./getRecurringInstanceDates"; + +// Export the function that creates a recurrence rule document in the database. export { createRecurrenceRule } from "./createRecurrenceRule"; + +// Export the function that generates recurring event instances based on recurrence rules and data. export { generateRecurringEventInstances } from "./generateRecurringEventInstances"; + +// Export the function that removes dangling recurrence rule and base recurring event documents if no associated events exist. export { removeDanglingDocuments } from "./removeDanglingDocuments"; diff --git a/src/helpers/event/recurringEventHelpers/removeDanglingDocuments.ts b/src/helpers/event/recurringEventHelpers/removeDanglingDocuments.ts index e2c2728d63..12a96dbe49 100644 --- a/src/helpers/event/recurringEventHelpers/removeDanglingDocuments.ts +++ b/src/helpers/event/recurringEventHelpers/removeDanglingDocuments.ts @@ -2,17 +2,14 @@ import type mongoose from "mongoose"; import { Event, RecurrenceRule } from "../../../models"; /** - * This function removes dangling recurrence rule and base recurring event documents. - * @param baseRecurringEventId - _id of the base recurring event. + * Removes dangling recurrence rule and base recurring event documents if they have no associated events. * @param recurrenceRuleId - _id of the recurrence rule. - * @remarks The following steps are followed: - * 1. Call the function associated with the document to be removed, i.e. removeRecurrenceRule or removeBaseRecurringEvent. - * 2. Check if the document has any associations, i.e.: - * - for recurrence rule, check if there exist any event that follow this given recurrence rule - * - for base recurring event, check if there exist any event that has this event as its base recurring event - * 3. Remove the documents if no associations are found. + * @param baseRecurringEventId - _id of the base recurring event. + * @param session - Mongoose client session. + * @remarks + * The function first checks if there are any associated events for each document. + * If no associated events are found, it deletes the document from the database. */ - export const removeDanglingDocuments = async ( recurrenceRuleId: string, baseRecurringEventId: string, @@ -22,6 +19,11 @@ export const removeDanglingDocuments = async ( await removeBaseRecurringEvent(baseRecurringEventId, session); }; +/** + * Removes the recurrence rule document if no events follow this recurrence rule. + * @param recurrenceRuleId - _id of the recurrence rule. + * @param session - Mongoose client session. + */ const removeRecurrenceRule = async ( recurrenceRuleId: string, session: mongoose.ClientSession, @@ -40,6 +42,11 @@ const removeRecurrenceRule = async ( } }; +/** + * Removes the base recurring event document if no events reference this base recurring event. + * @param baseRecurringEventId - _id of the base recurring event. + * @param session - Mongoose client session. + */ const removeBaseRecurringEvent = async ( baseRecurringEventId: string, session: mongoose.ClientSession, diff --git a/src/helpers/event/updateEventHelpers/getEventData.ts b/src/helpers/event/updateEventHelpers/getEventData.ts index e0dfc157d7..d36c05bf8e 100644 --- a/src/helpers/event/updateEventHelpers/getEventData.ts +++ b/src/helpers/event/updateEventHelpers/getEventData.ts @@ -3,20 +3,17 @@ import type { UpdateEventInput } from "../../../types/generatedGraphQLTypes"; import type { InterfaceRecurringEvent } from "../recurringEventHelpers/generateRecurringEventInstances"; /** - * This function get the data to be used for generating the recurring event instances. - * @param updateEventInputData - the update event input data. - * @param event - the event to be updated. - * @remarks The following steps are followed: - * 1. get the current event data. - * 2. update the data provided in the input. + * This function retrieves the data to be used for updating an event, + * combining existing event data with new input data. + * @param updateEventInputData - The input data to update the event. + * @param event - The current event data to be updated. * @returns The updated event data. */ - export const getEventData = ( updateEventInputData: UpdateEventInput | undefined | null, event: InterfaceEvent, ): InterfaceRecurringEvent => { - // get the event's current data + // Step 1: Get the current event data. const eventCurrentData = { title: event.title, description: event.description, @@ -36,7 +33,7 @@ export const getEventData = ( organizationId: event.organization, }; - // get the updated event data + // Step 2: Update the current data with the input data (if provided). const updatedEventData: InterfaceRecurringEvent = { ...eventCurrentData, ...(updateEventInputData as Partial), diff --git a/src/helpers/event/updateEventHelpers/updateRecurringEvent.ts b/src/helpers/event/updateEventHelpers/updateRecurringEvent.ts index b0fa32e605..5fea172f62 100644 --- a/src/helpers/event/updateEventHelpers/updateRecurringEvent.ts +++ b/src/helpers/event/updateEventHelpers/updateRecurringEvent.ts @@ -11,29 +11,25 @@ import { } from "../../../constants"; /** - * This function updates the recurring event. - * @param args - update event args. - * @param event - the event to be updated. - * @remarks The following steps are followed: - * 1. get the recurrence rule. - * 2. get the base recurring event. - * 3. based on the type of update, call the function required. - * @returns The updated event. + * This function updates a recurring event based on the provided arguments. + * @param args - The arguments containing data for updating the event. + * @param event - The event to be updated. + * @param session - The Mongoose client session for database transactions. + * @returns The updated event object. */ - export const updateRecurringEvent = async ( args: MutationUpdateEventArgs, event: InterfaceEvent, session: mongoose.ClientSession, ): Promise => { - let updatedEvent: InterfaceEvent = event; + let updatedEvent: InterfaceEvent = event; // Initialize the updated event with the current event data - // get the recurrenceRule + // Step 1: Retrieve the recurrenceRule associated with the event const recurrenceRule = await RecurrenceRule.findOne({ _id: event.recurrenceRuleId, }); - // throws error if the recurrence rule doesn't exist + // Step 2: Throw an error if the recurrence rule is not found if (recurrenceRule === null) { throw new errors.NotFoundError( requestContext.translate(RECURRENCE_RULE_NOT_FOUND.MESSAGE), @@ -42,12 +38,12 @@ export const updateRecurringEvent = async ( ); } - // get the baseRecurringEvent + // Step 3: Retrieve the baseRecurringEvent associated with the event const baseRecurringEvent = await Event.findOne({ _id: event.baseRecurringEventId, }); - // throws error if the base recurring event doesn't exist + // Step 4: Throw an error if the base recurring event is not found if (baseRecurringEvent === null) { throw new errors.NotFoundError( requestContext.translate(BASE_RECURRING_EVENT_NOT_FOUND.MESSAGE), @@ -56,16 +52,17 @@ export const updateRecurringEvent = async ( ); } + // Step 5: Determine the type of update (this instance, all instances, this and following instances) if ( (args.data?.isRecurringEventException !== undefined && args.data?.isRecurringEventException !== event.isRecurringEventException) || args.recurringEventUpdateType === "thisInstance" ) { - // if this is a single update or if the event's exception status has changed + // Update only this instance or handle exception status change updatedEvent = await updateThisInstance(args, event, session); } else if (args.recurringEventUpdateType === "allInstances") { - // perform a regular bulk update on all the instances + // Update all instances updatedEvent = await updateRecurringEventInstances( args, event, @@ -75,7 +72,7 @@ export const updateRecurringEvent = async ( session, ); } else { - // update current and following events + // Update this and following instances updatedEvent = await updateRecurringEventInstances( args, event, diff --git a/src/index.ts b/src/index.ts index 318ea6e19b..03617bf6e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,6 +101,7 @@ const serverCleanup = useServer( { schema, context: (_ctx, _msg, _args) => ({ pubsub }) }, wsServer, ); +let serverHost = "localhost"; async function startServer(): Promise { await database.connect(); @@ -110,31 +111,39 @@ async function startServer(): Promise { app.use( "/graphql", expressMiddleware(server, { - context: async ({ req, res }) => ({ - ...isAuth(req), - req, - res, - pubsub, - apiRootUrl: `${req.protocol}://${req.get("host")}/`, - }), + context: async ({ req, res }) => { + serverHost = req.get("host") || "localhost"; + return { + ...isAuth(req), + req, + res, + pubsub, + apiRootUrl: `${req.protocol}://${serverHost}/`, + }; + }, }), ); // Modified server startup + const PORT = parseInt(SERVER_PORT || "4000", 10); + if (Number.isNaN(PORT) || PORT < 0 || PORT > 65535) { + throw new Error( + `Invalid SERVER_PORT: ${process.env.SERVER_PORT}. Please ensure it is a numeric value between 0 and 65535.`, + ); + } + await new Promise((resolve) => - httpServer.listen({ port: parseInt(SERVER_PORT as string) }, resolve), + httpServer.listen({ port: PORT }, resolve), ); // Log all the configuration related issues await logIssues(); logger.info( - `🚀 Server ready at ${ - process.env.NODE_ENV === "production" ? "https" : "http" - }://localhost:${SERVER_PORT}/graphql`, + `🚀 Server ready at ${process.env.NODE_ENV === "production" ? "https" : "http"}://${serverHost}:${PORT}/graphql`, ); logger.info( - `🚀 Subscription endpoint ready at ws://localhost:${SERVER_PORT}/graphql`, + `🚀 Subscription endpoint ready at ws://${serverHost}:${PORT}/graphql`, ); } diff --git a/src/libraries/dbLogger.ts b/src/libraries/dbLogger.ts index 7db246cd3c..d24aa02ae3 100644 --- a/src/libraries/dbLogger.ts +++ b/src/libraries/dbLogger.ts @@ -2,16 +2,26 @@ import { LOG, LOG_PATH, TransactionLogTypes } from "../constants"; import type { Query, Schema, Document } from "mongoose"; import winston from "winston"; +/** + * The structure of a transaction log entry. + */ export type TransactionLogInfo = { + /** The timestamp when the log entry was created */ timestamp: string; + /** The name of the model associated with the log entry */ model: string; + /** The type of transaction (e.g., create, update, delete) */ type: string; + /** The query executed (optional) */ query?: string; + /** The update performed (optional) */ update?: string; }; +// Initialize the database logger to null let dbLogger: winston.Logger | null = null; +// If logging is enabled and a log path is specified, create a Winston logger if (LOG && LOG_PATH) { dbLogger = winston.createLogger({ level: "info", @@ -29,22 +39,45 @@ if (LOG && LOG_PATH) { }); } +/** + * Interface for a document that includes logging information. + */ export interface InterfaceLoggableDocument extends Document { + /** Information about the transaction log */ logInfo: TransactionLogInfo; } +/** + * Interface for a query that can include logging information. + */ export interface InterfaceLoggableQuery extends Query { + /** Information about the transaction log (optional) */ logInfo?: TransactionLogInfo; } +/** + * Creates a logging middleware for a Mongoose schema. This middleware logs + * create, update, and delete operations on the specified schema. + * + * @param schema - The Mongoose schema to which the middleware will be added + * @param modelName - The name of the model associated with the schema + */ export function createLoggingMiddleware( schema: Schema, modelName: string, ): void { + // If no logger is configured, exit the function early if (!dbLogger) { return; } + /** + * Creates a log entry for a specific action. + * + * @param type - The type of transaction (create, update, delete) + * @param thisContext - The query context (optional) + * @returns A TransactionLogInfo object with details about the transaction + */ const logAction = ( type: TransactionLogTypes, thisContext?: InterfaceLoggableQuery, @@ -61,25 +94,30 @@ export function createLoggingMiddleware( }; }; + // Middleware to log "save" operations before they occur schema.pre("save", function (next) { this.logInfo = logAction(TransactionLogTypes.CREATE); next(); }); + // Middleware to log "save" operations after they occur schema.post("save", function () { if (dbLogger) { dbLogger.info("success", this.logInfo); } }); + // List of update operations to log const updateOperations: ("findOneAndUpdate" | "updateOne" | "updateMany")[] = ["findOneAndUpdate", "updateOne", "updateMany"]; updateOperations.forEach((operation) => { + // Middleware to log update operations before they occur schema.pre(operation, function (this: InterfaceLoggableQuery, next) { this.logInfo = logAction(TransactionLogTypes.UPDATE, this); next(); }); + // Middleware to log update operations after they occur schema.post(operation, function (this: InterfaceLoggableQuery) { if (dbLogger) { dbLogger?.info("success", this.logInfo); @@ -87,16 +125,19 @@ export function createLoggingMiddleware( }); }); + // List of delete operations to log const deleteOperations: ("deleteOne" | "deleteMany")[] = [ "deleteOne", "deleteMany", ]; deleteOperations.forEach((operation) => { + // Middleware to log delete operations before they occur schema.pre(operation, function (this: InterfaceLoggableQuery, next) { this.logInfo = logAction(TransactionLogTypes.DELETE, this); next(); }); + // Middleware to log delete operations after they occur schema.post(operation, function (this: InterfaceLoggableQuery) { if (dbLogger) { dbLogger.info("success", this.logInfo); @@ -105,4 +146,5 @@ export function createLoggingMiddleware( }); } +// Export the logger as the default export export default dbLogger; diff --git a/src/libraries/errors/ImageSizeLimitExceeded.ts b/src/libraries/errors/ImageSizeLimitExceeded.ts index 2d0748fa3b..9e6e7dfdc3 100644 --- a/src/libraries/errors/ImageSizeLimitExceeded.ts +++ b/src/libraries/errors/ImageSizeLimitExceeded.ts @@ -1,8 +1,18 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects invalid file type errors and sends those errors to the superclass ApplicationError. + * This class represents an error indicating that the image size limit has been exceeded. + * It extends the ApplicationError class to handle and format the error information. */ export class ImageSizeLimitExceeded extends ApplicationError { + /** + * Creates an instance of ImageSizeLimitExceeded. + * + * @param message - The error message (default is "Image Size Limit Exceeded"). + * @param code - Optional error code (default is null). + * @param param - Optional parameter associated with the error (default is null). + * @param metadata - Optional additional metadata associated with the error (default is an empty object). + */ constructor( message = "Image Size Limit Exceeded", code: string | null = null, @@ -10,6 +20,7 @@ export class ImageSizeLimitExceeded extends ApplicationError { // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata: Record = {}, ) { + // Construct the error JSON in the required format for ApplicationError const errorJson = [ { message, @@ -18,6 +29,8 @@ export class ImageSizeLimitExceeded extends ApplicationError { metadata, }, ]; + + // Call the superclass ApplicationError constructor with the formatted error JSON super(errorJson, 403, message); } } diff --git a/src/libraries/errors/applicationError.ts b/src/libraries/errors/applicationError.ts index 6130ba1e2c..dcfabfb216 100644 --- a/src/libraries/errors/applicationError.ts +++ b/src/libraries/errors/applicationError.ts @@ -1,19 +1,37 @@ +/** + * Interface representing the structure of an error. + */ export interface InterfaceError { + /** The error message */ message: string; + /** The error code, can be null */ code: string | null; + /** The parameter associated with the error, can be null */ param: string | null; + /** Optional additional metadata associated with the error */ metadata?: Record; } + /** - * This class is responsible for finding the application errors. It adds those errors to superclass called Error. + * This class is responsible for handling application errors. + * It extends the built-in Error class to include additional properties and methods. */ export class ApplicationError extends Error { + /** An array of errors conforming to the InterfaceError interface */ public errors: InterfaceError[]; + /** The HTTP status code associated with the error */ public httpCode; + /** + * Creates an instance of ApplicationError. + * + * @param errors - An array of errors conforming to the InterfaceError interface. + * @param httpCode - The HTTP status code associated with the error (default is 422). + * @param message - The error message (default is "Error"). + */ constructor(errors: InterfaceError[], httpCode = 422, message = "Error") { - super(message); - this.errors = errors; - this.httpCode = httpCode; + super(message); // Call the constructor of the superclass Error + this.errors = errors; // Assign the errors to the instance + this.httpCode = httpCode; // Assign the HTTP status code to the instance } } diff --git a/src/libraries/errors/conflictError.ts b/src/libraries/errors/conflictError.ts index a80c6b106c..d47f074594 100644 --- a/src/libraries/errors/conflictError.ts +++ b/src/libraries/errors/conflictError.ts @@ -1,8 +1,17 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects conflict errors and sends those errors to the superclass ApplicationError. + * This class represents a conflict error. It extends the ApplicationError class + * and is used to handle situations where a conflicting entry is found. */ export class ConflictError extends ApplicationError { + /** + * Creates an instance of ConflictError. + * @param message - The error message. Defaults to "Conflicting entry found". + * @param code - The error code. Can be null. Defaults to null. + * @param param - The parameter related to the error. Can be null. Defaults to null. + * @param metadata - Additional metadata related to the error. Defaults to an empty object. + */ constructor( message = "Conflicting entry found", code: string | null = null, diff --git a/src/libraries/errors/inputValidationError.ts b/src/libraries/errors/inputValidationError.ts index 475ef31b91..d94dcee798 100644 --- a/src/libraries/errors/inputValidationError.ts +++ b/src/libraries/errors/inputValidationError.ts @@ -1,8 +1,17 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects input validation errors and sends those errors to the superclass ApplicationError. + * Represents an input validation error. It extends the ApplicationError class + * and is used to handle errors related to input validation failures. */ export class InputValidationError extends ApplicationError { + /** + * Creates an instance of InputValidationError. + * @param message - The error message. Defaults to "InputValidationError". + * @param code - The error code. Can be null. Defaults to null. + * @param param - The parameter related to the error. Can be null. Defaults to null. + * @param metadata - Additional metadata related to the error. Defaults to an empty object. + */ constructor( message = "InputValidationError", code: string | null = null, diff --git a/src/libraries/errors/internalServerError.ts b/src/libraries/errors/internalServerError.ts index 8d5bf4f87d..f99c0707a4 100644 --- a/src/libraries/errors/internalServerError.ts +++ b/src/libraries/errors/internalServerError.ts @@ -1,8 +1,18 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects internal server errors and sends those errors to the superclass ApplicationError. + * This class represents an error indicating an internal server error. + * It extends the ApplicationError class to handle and format the error information. */ export class InternalServerError extends ApplicationError { + /** + * Creates an instance of InternalServerError. + * + * @param message - The error message (default is "Internal Server Error!"). + * @param code - Optional error code (default is null). + * @param param - Optional parameter associated with the error (default is null). + * @param metadata - Optional additional metadata associated with the error (default is an empty object). + */ constructor( message = "Internal Server Error!", code: string | null = null, @@ -10,6 +20,7 @@ export class InternalServerError extends ApplicationError { // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata: Record = {}, ) { + // Construct the error JSON in the required format for ApplicationError const errorJson = [ { message, @@ -18,6 +29,8 @@ export class InternalServerError extends ApplicationError { metadata, }, ]; + + // Call the superclass ApplicationError constructor with the formatted error JSON super(errorJson, 500, message); } } diff --git a/src/libraries/errors/invalidFileTypeError.ts b/src/libraries/errors/invalidFileTypeError.ts index a3663ca745..32442e4911 100644 --- a/src/libraries/errors/invalidFileTypeError.ts +++ b/src/libraries/errors/invalidFileTypeError.ts @@ -1,8 +1,18 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects invalid file type errors and sends those errors to the superclass ApplicationError. + * This class represents an error indicating an invalid file type. + * It extends the ApplicationError class to handle and format the error information. */ export class InvalidFileTypeError extends ApplicationError { + /** + * Creates an instance of InvalidFileTypeError. + * + * @param message - The error message (default is "Invalid File Type"). + * @param code - Optional error code (default is null). + * @param param - Optional parameter associated with the error (default is null). + * @param metadata - Optional additional metadata associated with the error (default is an empty object). + */ constructor( message = "Invalid File Type", code: string | null = null, @@ -10,6 +20,7 @@ export class InvalidFileTypeError extends ApplicationError { // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata: Record = {}, ) { + // Construct the error JSON in the required format for ApplicationError const errorJson = [ { message, @@ -18,6 +29,8 @@ export class InvalidFileTypeError extends ApplicationError { metadata, }, ]; + + // Call the superclass ApplicationError constructor with the formatted error JSON super(errorJson, 403, message); } } diff --git a/src/libraries/errors/notFoundError.ts b/src/libraries/errors/notFoundError.ts index b4b6f45a89..68fbb0b4b0 100644 --- a/src/libraries/errors/notFoundError.ts +++ b/src/libraries/errors/notFoundError.ts @@ -1,8 +1,17 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects Not Found errors and sends those errors to the superclass ApplicationError. + * Represents a "Not Found" error. It extends the ApplicationError class + * and is used to handle situations where a requested resource is not found. */ export class NotFoundError extends ApplicationError { + /** + * Creates an instance of NotFoundError. + * @param message - The error message. Defaults to "Not Found". + * @param code - The error code. Can be null. Defaults to null. + * @param param - The parameter related to the error. Can be null. Defaults to null. + * @param metadata - Additional metadata related to the error. Defaults to an empty object. + */ constructor( message = "Not Found", code: string | null = null, diff --git a/src/libraries/errors/unauthenticatedError.ts b/src/libraries/errors/unauthenticatedError.ts index e090098c91..db55d6e2b0 100644 --- a/src/libraries/errors/unauthenticatedError.ts +++ b/src/libraries/errors/unauthenticatedError.ts @@ -1,8 +1,18 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects unauthenticated errors and sends those errors to the superclass ApplicationError. + * This class represents an error indicating an unauthenticated request. + * It extends the ApplicationError class to handle and format the error information. */ export class UnauthenticatedError extends ApplicationError { + /** + * Creates an instance of UnauthenticatedError. + * + * @param message - The error message (default is "UnauthenticatedError"). + * @param code - Optional error code (default is null). + * @param param - Optional parameter associated with the error (default is null). + * @param metadata - Optional additional metadata associated with the error (default is an empty object). + */ constructor( message = "UnauthenticatedError", code: string | null = null, @@ -10,6 +20,7 @@ export class UnauthenticatedError extends ApplicationError { // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata: Record = {}, ) { + // Construct the error JSON in the required format for ApplicationError const errorJson = [ { message, @@ -18,6 +29,8 @@ export class UnauthenticatedError extends ApplicationError { metadata, }, ]; + + // Call the superclass ApplicationError constructor with the formatted error JSON super(errorJson, 401, message); } } diff --git a/src/libraries/errors/unauthorizedError.ts b/src/libraries/errors/unauthorizedError.ts index e09902edca..64784a466a 100644 --- a/src/libraries/errors/unauthorizedError.ts +++ b/src/libraries/errors/unauthorizedError.ts @@ -1,8 +1,17 @@ import { ApplicationError } from "./applicationError"; + /** - * This class detects unauthorized errors and sends those errors to the superclass ApplicationError. + * Represents an unauthorized error. It extends the ApplicationError class + * and is used to handle situations where access to a resource is unauthorized. */ export class UnauthorizedError extends ApplicationError { + /** + * Creates an instance of UnauthorizedError. + * @param message - The error message. Defaults to "UnauthorizedError". + * @param code - The error code. Can be null. Defaults to null. + * @param param - The parameter related to the error. Can be null. Defaults to null. + * @param metadata - Additional metadata related to the error. Defaults to an empty object. + */ constructor( message = "UnauthorizedError", code: string | null = null, diff --git a/src/libraries/errors/validationError.ts b/src/libraries/errors/validationError.ts index 3f197e2fa0..94ee11255e 100644 --- a/src/libraries/errors/validationError.ts +++ b/src/libraries/errors/validationError.ts @@ -1,10 +1,19 @@ import type { InterfaceError } from "./applicationError"; import { ApplicationError } from "./applicationError"; + /** - * This class detects validation errors and sends those errors to the superclass ApplicationError. + * This class represents an error indicating validation errors. + * It extends the ApplicationError class to handle and format the error information. */ export class ValidationError extends ApplicationError { + /** + * Creates an instance of ValidationError. + * + * @param errors - An array of errors conforming to the InterfaceError interface (default is an empty array). + * @param message - The error message (default is "Validation error"). + */ constructor(errors: InterfaceError[] = [], message = "Validation error") { + // Call the superclass ApplicationError constructor with the provided errors and HTTP status code super(errors, 422, message); } } diff --git a/src/libraries/index.ts b/src/libraries/index.ts index 4c633e4375..69152ddf47 100644 --- a/src/libraries/index.ts +++ b/src/libraries/index.ts @@ -1,4 +1,11 @@ +// Export all contents from the "errors" module as "errors" export * as errors from "./errors"; + +// Export all named exports from the "logger" module export * from "./logger"; + +// Export all contents from the "requestContext" module as "requestContext" export * as requestContext from "./requestContext"; + +// Export all contents from the "requestTracing" module as "requestTracing" export * as requestTracing from "./requestTracing"; diff --git a/src/libraries/logger.ts b/src/libraries/logger.ts index 194d627511..2901b17106 100644 --- a/src/libraries/logger.ts +++ b/src/libraries/logger.ts @@ -3,14 +3,16 @@ import { createLogger, transports, format } from "winston"; import { getTracingId } from "./requestTracing"; import { appConfig } from "../config"; +// Destructure the necessary format functions from winston.format const { combine, printf, splat, colorize, simple, timestamp } = format; +// Define log formats with and without colorization const formats = { colorized: combine( - colorize(), - splat(), - simple(), - timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + colorize(), // Add colors to log levels + splat(), // Allow string interpolation in log messages + simple(), // Simplify log message format + timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // Add timestamp in specified format printf( (info) => `${info.level || "-"} ${info.timestamp || "-"} ${ @@ -22,13 +24,13 @@ const formats = { : JSON.stringify( _.omit(info, ["level", "message", "stack", "timestamp"]), ) - } ${info.stack || ""}`, + } ${info.stack || ""}`, // Custom log message format ), ), non_colorized: combine( - splat(), - simple(), - timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + splat(), // Allow string interpolation in log messages + simple(), // Simplify log message format + timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // Add timestamp in specified format printf( (info) => `${info.level || "-"} ${info.timestamp || "-"} ${ @@ -40,28 +42,30 @@ const formats = { : JSON.stringify( _.omit(info, ["level", "message", "stack", "timestamp"]), ) - } ${info.stack || ""}`, + } ${info.stack || ""}`, // Custom log message format ), ), }; +// Create a Winston logger with a console transport const logger = createLogger({ transports: [ new transports.Console({ - level: appConfig.log_level, + level: appConfig.log_level, // Set log level from app configuration format: appConfig.colorize_logs === "true" - ? formats.colorized - : formats.non_colorized, + ? formats.colorized // Use colorized format if enabled in config + : formats.non_colorized, // Use non-colorized format otherwise }), ], }); -// The code block shifted before exporting logger +// Define a stream for use with other logging systems, such as morgan const stream = { write: (message: string | null): void => { - logger.info((message || "").trim()); + logger.info((message || "").trim()); // Log the message using the info level }, }; +// Export the logger and stream export { logger, stream }; diff --git a/src/libraries/requestContext.ts b/src/libraries/requestContext.ts index 513cadc631..3edb66081a 100644 --- a/src/libraries/requestContext.ts +++ b/src/libraries/requestContext.ts @@ -3,23 +3,42 @@ import cls from "cls-hooked"; import type { NextFunction, Request, Response } from "express"; import i18n from "i18n"; +// Create a namespace for managing request context export const requestContextNamespace = cls.createNamespace( "talawa-request-context", ); +/** + * Sets a value in the request context. + * @param key - The key under which the value is stored. + * @param value - The value to store. + * @returns The stored value. + */ export const setRequestContextValue = (key: string, value: T): T => { return requestContextNamespace.set(key, value); }; +/** + * Gets a value from the request context. + * @param key - The key under which the value is stored. + * @returns The retrieved value. + */ export const getRequestContextValue = (key: string): T => { return requestContextNamespace.get(key); }; +/** + * Sets the translation functions in the request context. + * @param obj - The object containing translation functions. + */ export const setRequestContext = (obj: any): void => { setRequestContextValue("translate", obj.__); setRequestContextValue("translatePlural", obj.__n); }; +/** + * Middleware to bind the request and response to the request context namespace. + */ export const middleware = () => { return (req: Request, res: Response, next: NextFunction): void => { requestContextNamespace.bindEmitter(req); @@ -31,10 +50,18 @@ export const middleware = () => { }; }; +/** + * Interface for initialization options. + */ interface InterfaceInitOptions extends Record { requestHandler?: () => T; } +/** + * Initializes the request context and i18n. + * @param options - The initialization options. + * @returns The result of the request handler or an empty object if not provided. + */ export const init = (options: InterfaceInitOptions = {}): T => { const obj: any = {}; @@ -65,18 +92,28 @@ export const init = (options: InterfaceInitOptions = {}): T => { }); }; +/** + * Translates a string using the current context's translation function. + * @param args - The arguments to pass to the translation function. + * @returns The translated string. + */ export const translate = (...args: any): any => { const _ = getRequestContextValue("translate"); if (typeof _ !== "function") { throw new Error("i18n is not initialized, try app.use(i18n.init);"); } - return _(args); + return args.map((arg: any) => _(arg)).join(","); }; +/** + * Translates a plural string using the current context's translation function. + * @param args - The arguments to pass to the translation function. + * @returns The translated string. + */ export const translatePlural = (...args: any): any => { const _n = getRequestContextValue("translatePlural"); if (typeof _n !== "function") { throw new Error("i18n is not initialized, try app.use(i18n.init);"); } - return _n(args); + return args.map((arg: any) => _n(arg)).join(","); }; diff --git a/src/libraries/requestTracing.ts b/src/libraries/requestTracing.ts index 1fb7b15229..bcb63841e0 100644 --- a/src/libraries/requestTracing.ts +++ b/src/libraries/requestTracing.ts @@ -8,28 +8,52 @@ import type { NextFunction, Request, Response } from "express"; // Alphabets used in the custom nanoid function const alphabets = "0123456789abcdefghijklmnopqrstuvwxyz"; -/* -Custom nanoid function to generate a unique 10 characters request ID -using the characters in alphabets variable -*/ +/** + * Custom nanoid function to generate a unique 10 characters request ID + * using the characters in the alphabets variable. + */ const nanoid = customAlphabet(alphabets, 10); +/** + * Namespace for request tracing to maintain context across asynchronous operations. + */ export const requestTracingNamespace = cls.createNamespace("request-tracing"); +// Initialize cls-bluebird with the request tracing namespace clsBluebird(requestTracingNamespace); +/** + * Name of the header where the tracing ID will be stored. + */ export const tracingIdHeaderName = "X-Tracing-Id"; +/** + * Key name for storing the tracing ID in the namespace context. + */ const tracingIdContextKeyName = "tracingId"; +/** + * Sets the tracing ID in the namespace context. + * @param tracingId - The tracing ID to set. + * @returns The tracing ID that was set. + */ export const setTracingId = (tracingId: string): string => { return requestTracingNamespace.set(tracingIdContextKeyName, tracingId); }; +/** + * Gets the tracing ID from the namespace context. + * @returns The tracing ID. + */ export const getTracingId = (): string => { return requestTracingNamespace.get(tracingIdContextKeyName) as string; }; +/** + * Middleware to handle request tracing. It generates or retrieves a tracing ID, + * sets it in the headers of the request and response, and stores it in the namespace context. + * @returns A middleware function. + */ export const middleware = () => { return (req: Request, res: Response, next: NextFunction): void => { requestTracingNamespace.bindEmitter(req); @@ -47,6 +71,13 @@ export const middleware = () => { }; }; +/** + * Runs a method within the context of a tracing ID. If a tracing ID is provided, it uses that ID; + * otherwise, it generates a new one. + * @param tracingId - The tracing ID to use. + * @param method - The method to run within the context of the tracing ID. + * @returns A promise that resolves when the method completes. + */ export const trace = async ( tracingId: string, method: () => T, diff --git a/src/libraries/validators/compareDates.ts b/src/libraries/validators/compareDates.ts index e95cf9a8ee..53bafa5735 100644 --- a/src/libraries/validators/compareDates.ts +++ b/src/libraries/validators/compareDates.ts @@ -1,9 +1,23 @@ +/** + * Compares two dates and returns a message if the first date is later than the second date. + * + * @param date1 - The first date as a string. + * @param date2 - The second date as a string. + * @returns A message indicating that the start date must be earlier than the end date, or an empty string if the dates are in the correct order. + */ export function compareDates(date1: string, date2: string): string { + // Convert the date strings to Date objects const dateObj1 = new Date(date1); const dateObj2 = new Date(date2); + + // Calculate the difference in time between the two dates const result = dateObj1.getTime() - dateObj2.getTime(); + + // If the first date is later than the second date, return an error message if (result > 0) { return `start date must be earlier than end date`; } + + // Return an empty string if the dates are in the correct order return ""; } diff --git a/src/libraries/validators/compareTime.ts b/src/libraries/validators/compareTime.ts index b3442da6ed..11f401bddf 100644 --- a/src/libraries/validators/compareTime.ts +++ b/src/libraries/validators/compareTime.ts @@ -1,9 +1,23 @@ +/** + * Compares two times and returns a message if the first time is later than the second time. + * + * @param time1 - The first time as a string. + * @param time2 - The second time as a string. + * @returns A message indicating that the start time must be earlier than the end time, or an empty string if the times are in the correct order. + */ export function compareTime(time1: string, time2: string): string { + // Convert the time strings to Date objects const timeObj1 = new Date(time1); const timeObj2 = new Date(time2); + + // Calculate the difference in hours between the two times const result = timeObj1.getHours() - timeObj2.getHours(); + + // If the first time is later than the second time, return an error message if (result > 0) { return `start time must be earlier than end time`; } + + // Return an empty string if the times are in the correct order return ""; } diff --git a/src/libraries/validators/validateString.ts b/src/libraries/validators/validateString.ts index eff049c5c1..c5076a3762 100644 --- a/src/libraries/validators/validateString.ts +++ b/src/libraries/validators/validateString.ts @@ -1,3 +1,10 @@ +/** + * Checks if a given string is less than a specified maximum length. + * + * @param str - The string to check. + * @param maxLength - The maximum allowed length of the string. + * @returns An object containing a boolean indicating if the string is less than the maximum length. + */ export function isValidString( str: string, maxLength: number, diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 7f43ff4d06..065f1a55bc 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1 +1,2 @@ +// Export everything from this module, including isAuth function export * from "./isAuth"; diff --git a/src/middleware/isAuth.ts b/src/middleware/isAuth.ts index 570d9d9d28..7e96dcdf39 100644 --- a/src/middleware/isAuth.ts +++ b/src/middleware/isAuth.ts @@ -9,43 +9,36 @@ export interface InterfaceAuthData { expired: boolean | undefined; userId: string | undefined; } + /** * This function determines whether the user is authorised and whether the access token has expired. - * @param Request - User Request + * @param request - User Request object from Express. * @returns Returns `authData` object with `isAuth`, `expired` and `userId` properties. */ export const isAuth = (request: Request): InterfaceAuthData => { - /* - This object is the return value of this function. Mutate the fields of this - object conditionally as the authentication flow continues and return it from - the function whereever needed. - */ + // Initialize authData object with default values const authData: InterfaceAuthData = { isAuth: false, expired: undefined, userId: undefined, }; - // This checks to see if there is an authorization field within the incoming request + // Retrieve authorization header from request const authHeader = request.headers.authorization; - // If no authorization header was sent from the client + // If no authorization header is present, return default authData if (!authHeader) { return authData; } - // format of request sent will be Bearer tokenvalue - // this splits it into two values bearer and the token + // Extract token from authorization header const token = authHeader.split(" ")[1]; - // if the token is null or an empty string + // If token is missing or empty, return default authData if (!token || token === "") { return authData; } - // uses key created in the auth resolver - // to be changed in production - // only tokens created with this key will be valid tokens // eslint-disable-next-line @typescript-eslint/no-explicit-any let decodedToken: any; try { @@ -70,16 +63,16 @@ export const isAuth = (request: Request): InterfaceAuthData => { return authData; } - // if the decoded token is not set + // If decoded token is not set, log an info message and return default authData if (!decodedToken) { logger.info("decoded token is not present"); return authData; } - // shows the user is an authenticated user + // Set isAuth to true and extract userId from decoded token authData.isAuth = true; - // pulls user data(userId) out of the token and attaches it to userId field of authData object authData.userId = decodedToken.userId; + // Return the finalized authData object return authData; }; diff --git a/src/models/ActionItem.ts b/src/models/ActionItem.ts index 31f0324e2c..ac9d191012 100644 --- a/src/models/ActionItem.ts +++ b/src/models/ActionItem.ts @@ -6,9 +6,8 @@ import type { InterfaceActionItemCategory } from "./ActionItemCategory"; import { MILLISECONDS_IN_A_WEEK } from "../constants"; /** - * This is an interface that represents a database(MongoDB) document for ActionItem [test change]. + * Interface representing a database document for ActionItem in MongoDB. */ - export interface InterfaceActionItem { _id: Types.ObjectId; assigneeId: PopulatedDoc; @@ -27,22 +26,21 @@ export interface InterfaceActionItem { } /** - * This describes the schema for a `ActionItem` that corresponds to `InterfaceActionItem` document. - * @param assigneeId - User to whom the ActionItem is assigned, refer to `User` model. - * @param assignerId - User who assigned the ActionItem, refer to the `User` model. - * @param actionItemCategoryId - ActionItemCategory to which the ActionItem is related, refer to the `ActionItemCategory` model. - * @param preCompletionNotes - Notes prior to completion. - * @param postCompletionNotes - Notes on completion. - * @param assignmentDate - Date of assignment. - * @param dueDate - Due date. - * @param completionDate - Completion date. - * @param isCompleted - Whether the ActionItem has been completed. - * @param eventId - Event to which the ActionItem is related, refer to the `Event` model. - * @param creatorId - User who created the ActionItem, refer to the `User` model. + * Defines the schema for the ActionItem document. + * @param assigneeId - User to whom the ActionItem is assigned. + * @param assignerId - User who assigned the ActionItem. + * @param actionItemCategoryId - ActionItemCategory to which the ActionItem belongs. + * @param preCompletionNotes - Notes recorded before completion. + * @param postCompletionNotes - Notes recorded after completion. + * @param assignmentDate - Date when the ActionItem was assigned. + * @param dueDate - Due date for the ActionItem. + * @param completionDate - Date when the ActionItem was completed. + * @param isCompleted - Flag indicating if the ActionItem is completed. + * @param eventId - Optional: Event to which the ActionItem is related. + * @param creatorId - User who created the ActionItem. * @param createdAt - Timestamp when the ActionItem was created. * @param updatedAt - Timestamp when the ActionItem was last updated. */ - const actionItemSchema = new Schema( { assigneeId: { @@ -96,9 +94,13 @@ const actionItemSchema = new Schema( required: true, }, }, - { timestamps: true }, + { timestamps: true }, // Automatic timestamps for createdAt and updatedAt fields ); +/** + * Retrieves or creates the ActionItem model. + * Prevents Mongoose OverwriteModelError during testing. + */ const actionItemModel = (): Model => model("ActionItem", actionItemSchema); diff --git a/src/models/ActionItemCategory.ts b/src/models/ActionItemCategory.ts index 314d845511..e49506c2f7 100644 --- a/src/models/ActionItemCategory.ts +++ b/src/models/ActionItemCategory.ts @@ -4,9 +4,8 @@ import type { InterfaceUser } from "./User"; import type { InterfaceOrganization } from "./Organization"; /** - * This is an interface that represents a database(MongoDB) document for ActionItemCategory (~Test Check). + * Represents a database document for ActionItemCategory in MongoDB. */ - export interface InterfaceActionItemCategory { _id: Types.ObjectId; name: string; @@ -18,15 +17,14 @@ export interface InterfaceActionItemCategory { } /** - * This describes the schema for a `actionItemCategory` that corresponds to `InterfaceCategory` document. - * @param name - An actionItemCategory to be selected for ActionItems. - * @param organizationId - Organization the actionItemCategory belongs to, refer to the `Organization` model. - * @param isDisabled - Whether actionItemCategory is disabled or not. - * @param creatorId - Task creator, refer to `User` model. - * @param createdAt - Time stamp of data creation. - * @param updatedAt - Time stamp of data updation. + * Mongoose schema definition for ActionItemCategory document. + * @param name - The name of the action item category. + * @param organizationId - The ID of the organization the category belongs to, referencing the Organization model. + * @param isDisabled - Indicates if the action item category is disabled. + * @param creatorId - The ID of the user who created the action item category, referencing the User model. + * @param createdAt - Timestamp of when the data was created. + * @param updatedAt - Timestamp of when the data was last updated. */ - const actionItemCategorySchema = new Schema( { name: { @@ -52,11 +50,15 @@ const actionItemCategorySchema = new Schema( { timestamps: true }, ); +// Indexing for organizationId and name to ensure uniqueness actionItemCategorySchema.index( { organizationId: 1, name: 1 }, { unique: true }, ); +/** + * Returns the Mongoose Model for ActionItemCategory to prevent OverwriteModelError. + */ const actionItemCategoryModel = (): Model => model( "ActionItemCategory", diff --git a/src/models/Advertisement.ts b/src/models/Advertisement.ts index 06ef2a5d81..a7fb157bd9 100644 --- a/src/models/Advertisement.ts +++ b/src/models/Advertisement.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceOrganization } from "./Organization"; + /** - * This is an interface, that represents database - (MongoDB) document for Advertisement. + * Interface representing a database document for Advertisement in MongoDB. */ export interface InterfaceAdvertisement { _id: string; @@ -97,14 +98,20 @@ const advertisementSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatic timestamps for createdAt and updatedAt fields }, ); +// Ensure uniqueness of combination of organizationId and name advertisementSchema.index({ organizationId: 1, name: 1 }, { unique: true }); +// Add logging middleware for Advertisement schema createLoggingMiddleware(advertisementSchema, "Advertisement"); +/** + * Retrieves or creates the Advertisement model. + * Prevents Mongoose OverwriteModelError during testing. + */ const advertisementModel = (): Model => model("Advertisement", advertisementSchema); diff --git a/src/models/AgendaCategory.ts b/src/models/AgendaCategory.ts index 021f5614e5..1eb4be5b9e 100644 --- a/src/models/AgendaCategory.ts +++ b/src/models/AgendaCategory.ts @@ -1,9 +1,10 @@ -import type { PopulatedDoc, Model, Types } from "mongoose"; +import type { PopulatedDoc, Model, Types, Document } from "mongoose"; import { Schema, model, models } from "mongoose"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; + /** - * This is an interface representing a document for an agenda category in the database (MongoDB). + * Represents a document for an agenda category in the MongoDB database. */ export interface InterfaceAgendaCategory { _id: Types.ObjectId; // Unique identifier for the agenda category. @@ -12,13 +13,15 @@ export interface InterfaceAgendaCategory { organizationId: PopulatedDoc; // Reference to the organization associated with the agenda category. createdBy: PopulatedDoc; // Reference to the user who created the agenda category. updatedBy: PopulatedDoc; // Reference to the user who last updated the agenda category. + createdAt: Date; // Date when the agenda category was created. + updatedAt: Date; // Date when the agenda category was last updated. } /** - * This is the Mongoose schema for an agenda category (test-change). + * Mongoose schema definition for an agenda category document. * @param name - Name of the agenda category. * @param description - Optional description of the agenda category. - * @param organization - Reference to the organization associated with the agenda category. + * @param organizationId - Reference to the organization associated with the agenda category. * @param createdBy - Reference to the user who created the agenda category. * @param updatedBy - Reference to the user who last updated the agenda category. * @param createdAt - Date when the agenda category was created. @@ -58,6 +61,9 @@ export const AgendaCategorySchema = new Schema({ }, }); +/** + * Returns the Mongoose Model for AgendaCategory to prevent OverwriteModelError. + */ const agendaCategoryModel = (): Model => model("AgendaCategory", AgendaCategorySchema); diff --git a/src/models/AgendaItem.ts b/src/models/AgendaItem.ts index f2f399eb4d..fb3cc54d9b 100644 --- a/src/models/AgendaItem.ts +++ b/src/models/AgendaItem.ts @@ -7,7 +7,7 @@ import type { InterfaceEvent } from "./Event"; import type { InterfaceNote } from "./Note"; /** - * This is an interface representing a document for an agenda item in the database (MongoDB). + * Represents a document for an agenda item in the MongoDB database. */ export interface InterfaceAgendaItem { _id: Types.ObjectId; // Unique identifier for the agenda item. @@ -38,24 +38,23 @@ export enum ItemType { } /** - * This is the Mongoose schema for an agenda item. + * Mongoose schema definition for an agenda item document. * @param title - Title of the agenda item. * @param description - Optional description of the agenda item. - * @param relatedEvent - Reference to the event associated with the agenda item. + * @param relatedEventId - Reference to the event associated with the agenda item. * @param duration - Duration of the agenda item. * @param attachments - Optional array of attachment URLs. * @param createdBy - Reference to the user who created the agenda item. * @param updatedBy - Reference to the user who last updated the agenda item. - * @param urls - Optional users array indicating key note users for the agenda item. - * @param users - Optional user associated with the agenda item. + * @param urls - Optional array of URLs related to the agenda item. + * @param users - Optional array of users associated with the agenda item. * @param categories - Optional array of agenda categories associated with the agenda item. * @param sequence - Sequence number of the agenda item. * @param itemType - Type of the agenda item (Regular or Note). * @param createdAt - Date when the agenda item was created. * @param updatedAt - Date when the agenda item was last updated. - * @param isNote - Indicates whether the agenda item is a note. - * @param organization - Reference to the organization associated with the agenda item. - * @param notes - Reference to the notes associated with the agenda item. + * @param organizationId - Reference to the organization associated with the agenda item. + * @param notes - Array of notes associated with the agenda item. */ export const AgendaItemSchema = new Schema({ title: { @@ -114,7 +113,7 @@ export const AgendaItemSchema = new Schema({ type: Date, // required: true, }, - organization: { + organizationId: { type: Schema.Types.ObjectId, ref: "Organization", }, @@ -126,6 +125,9 @@ export const AgendaItemSchema = new Schema({ ], }); +/** + * Returns the Mongoose Model for AgendaItem to prevent OverwriteModelError. + */ const agendaItemModel = (): Model => model("AgendaItem", AgendaItemSchema); diff --git a/src/models/AgendaSection.ts b/src/models/AgendaSection.ts index 551ba66c16..69f2ad2c16 100644 --- a/src/models/AgendaSection.ts +++ b/src/models/AgendaSection.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; import { type InterfaceAgendaItem } from "./AgendaItem"; import type { InterfaceEvent } from "./Event"; + /** - * This is an interface representing a document for an agenda section in the database (MongoDB). + * Interface representing a document for an agenda section in MongoDB. */ export interface InterfaceAgendaSection { _id: Types.ObjectId; // Unique identifier for the agenda section. @@ -31,7 +32,7 @@ export interface InterfaceAgendaSection { export const AgendaSectionSchema = new Schema({ relatedEvent: { type: Schema.Types.ObjectId, - ref: "Event", + ref: "Event", // Refers to the Event model }, description: { type: String, @@ -40,7 +41,7 @@ export const AgendaSectionSchema = new Schema({ items: [ { type: Schema.Types.ObjectId, - ref: "AgendaItem", + ref: "AgendaItem", // Refers to the AgendaItem model }, ], sequence: { @@ -49,7 +50,7 @@ export const AgendaSectionSchema = new Schema({ }, createdBy: { type: Schema.Types.ObjectId, - ref: "User", + ref: "User", // Refers to the User model (creator) }, createdAt: { type: Date, @@ -61,9 +62,13 @@ export const AgendaSectionSchema = new Schema({ }, }); +/** + * Retrieves or creates the Mongoose model for AgendaSection. + * Prevents Mongoose OverwriteModelError during testing. + */ const agendaSectionModel = (): Model => model("AgendaSection", AgendaSectionSchema); -// Check if the AgendaItem model is already defined, if not, create a new one +// Export the AgendaSection model, preventing overwrite during tests export const AgendaSectionModel = (models.AgendaSection || agendaSectionModel()) as ReturnType; diff --git a/src/models/AppUserProfile.ts b/src/models/AppUserProfile.ts index cce4590ad3..ce0c07eb03 100644 --- a/src/models/AppUserProfile.ts +++ b/src/models/AppUserProfile.ts @@ -19,18 +19,18 @@ export interface InterfaceAppUserProfile { isSuperAdmin: boolean; } /** - * This describes the schema for a `AppUserProfile` that corresponds to `InterfaceAppUserProfile` document. - * @param user - User id of the AppUserProfile - * @param adminFor - Collection of organization where appuser is admin, each object refer to `Organization` model. - * @param appLanguageCode - AppUser's app language code. - * @param createdEvents - Collection of all events created by the user, each object refer to `Event` model. - * @param createdOrganizations - Collection of all organization created by the user, each object refer to `Organization` model. - * @param eventAdmin - Collection of the event admins, each object refer to `Event` model. - * @param pluginCreationAllowed - Wheather user is allowed to create plugins. - * @param tokenVersion - Token version. - * @param isSuperAdmin - Wheather user is super admin. - * @param token - Access token. - * */ + * Mongoose schema for an application user profile. + * @param userId - Reference to the user associated with the profile. + * @param adminFor - Array of organizations where the user is an admin. + * @param appLanguageCode - Language code preference of the app user. + * @param createdEvents - Array of events created by the user. + * @param createdOrganizations - Array of organizations created by the user. + * @param eventAdmin - Array of events where the user is an admin. + * @param pluginCreationAllowed - Flag indicating if user is allowed to create plugins. + * @param tokenVersion - Token version for authentication. + * @param isSuperAdmin - Flag indicating if the user is a super admin. + * @param token - Access token associated with the user profile. + */ const appUserSchema = new Schema( { diff --git a/src/models/CheckIn.ts b/src/models/CheckIn.ts index ba08070654..3b0ea7b646 100644 --- a/src/models/CheckIn.ts +++ b/src/models/CheckIn.ts @@ -10,6 +10,9 @@ import { import { type InterfaceEventAttendee } from "./EventAttendee"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Represents a document for a check-in entry in the MongoDB database. + */ export interface InterfaceCheckIn { _id: Types.ObjectId; eventAttendeeId: PopulatedDoc; @@ -19,6 +22,14 @@ export interface InterfaceCheckIn { updatedAt: Date; } +/** + * Mongoose schema definition for a check-in document. + * @param eventAttendeeId - Reference to the event attendee associated with the check-in. + * @param time - Date and time of the check-in. + * @param feedbackSubmitted - Indicates if feedback was submitted for the check-in. + * @param createdAt - Date when the check-in entry was created. + * @param updatedAt - Date when the check-in entry was last updated. + */ const checkInSchema = new Schema( { eventAttendeeId: { @@ -38,17 +49,21 @@ const checkInSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Adds createdAt and updatedAt automatically }, ); -// We will also create an index here for faster database querying +// Create an index for faster querying by eventAttendeeId checkInSchema.index({ eventAttendeeId: 1, }); +// Apply logging middleware to the schema createLoggingMiddleware(checkInSchema, "CheckIn"); +/** + * Returns the Mongoose Model for CheckIn to prevent OverwriteModelError. + */ const checkInModel = (): Model => model("CheckIn", checkInSchema); diff --git a/src/models/CheckOut.ts b/src/models/CheckOut.ts index 3bd9e763c2..3ee11e684d 100644 --- a/src/models/CheckOut.ts +++ b/src/models/CheckOut.ts @@ -10,6 +10,9 @@ import { import { type InterfaceEventAttendee } from "./EventAttendee"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Interface representing a document for a check-out record in MongoDB. + */ export interface InterfaceCheckOut { _id: Types.ObjectId; eventAttendeeId: PopulatedDoc; @@ -18,31 +21,43 @@ export interface InterfaceCheckOut { updatedAt: Date; } +/** + * Mongoose schema for a check-out record. + * @param eventAttendeeId - Reference to the event attendee associated with the check-out. + * @param time - Time of the check-out. + * @param createdAt - Timestamp when the check-out record was created. + * @param updatedAt - Timestamp when the check-out record was last updated. + */ const checkOutSchema = new Schema( { eventAttendeeId: { type: Schema.Types.ObjectId, - ref: "EventAttendee", + ref: "EventAttendee", // Refers to the EventAttendee model required: true, }, time: { type: Date, required: true, - default: Date.now, + default: Date.now, // Default time is the current date/time }, }, { - timestamps: true, + timestamps: true, // Adds createdAt and updatedAt timestamps }, ); -// We will also create an index here for faster database querying +// Create an index for eventAttendeeId for optimized querying checkOutSchema.index({ eventAttendeeId: 1, }); +// Apply logging middleware for database operations on CheckOut collection createLoggingMiddleware(checkOutSchema, "CheckOut"); +/** + * Retrieves or creates the Mongoose model for CheckOut. + * Prevents Mongoose OverwriteModelError during testing. + */ const checkOutModel = (): Model => model("CheckOut", checkOutSchema); diff --git a/src/models/Comment.ts b/src/models/Comment.ts index 5d5150a359..0443b81210 100644 --- a/src/models/Comment.ts +++ b/src/models/Comment.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; import type { InterfacePost } from "./Post"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface representing a document for a comment in the database - (MongoDB). + * Represents a document for a comment in the MongoDB database. */ export interface InterfaceComment { _id: Types.ObjectId; @@ -17,16 +18,17 @@ export interface InterfaceComment { likeCount: number; status: string; } + /** - * This is the Structure of the Comments - * @param text - Text - * @param createdAt - Date when the comment was created - * @param creatorId - Comment Creator, refer to `User` model - * @param postId - Id of the post on which this comment is created - * @param likedBy - Liked by whom - * @param likeCount - No of likes - * @param status - whether the comment is active, blocked or deleted. - * @param updatedAt - Date when the comment was updated + * Mongoose schema definition for a comment document. + * @param text - Text content of the comment. + * @param createdAt - Date when the comment was created. + * @param creatorId - Reference to the user who created the comment. + * @param updatedAt - Date when the comment was last updated. + * @param postId - Reference to the post on which this comment is created. + * @param likedBy - Array of users who liked the comment. + * @param likeCount - Number of likes for the comment. + * @param status - Status of the comment (ACTIVE, BLOCKED, DELETED). */ const commentSchema = new Schema( { @@ -62,12 +64,16 @@ const commentSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Adds createdAt and updatedAt automatically }, ); +// Apply logging middleware to the schema createLoggingMiddleware(commentSchema, "Comment"); +/** + * Returns the Mongoose Model for Comment to prevent OverwriteModelError. + */ const commentModel = (): Model => model("Comment", commentSchema); diff --git a/src/models/Community.ts b/src/models/Community.ts index bc6ade46ce..d687748a0b 100644 --- a/src/models/Community.ts +++ b/src/models/Community.ts @@ -2,7 +2,7 @@ import { Schema, model, models } from "mongoose"; import type { Types, Model } from "mongoose"; /** - * This is an interface that represents a database(MongoDB) document for Community. + * Interface representing a document for a community in MongoDB. */ export interface InterfaceCommunity { _id: Types.ObjectId; @@ -18,25 +18,24 @@ export interface InterfaceCommunity { youTube: string; slack: string; reddit: string; - }; + }; // Object containing various social media URLs for the community. } /** - * This describes the schema for a `Community` that corresponds to `InterfaceCommunity` document. - * @param logoUrl - Community logo URL. - * @param socialMediaUrls - Social media URLs. + * Mongoose schema for a community. + * @param name - Name of the community. + * @param logoUrl - URL of the community's logo. + * @param websiteLink - URL of the community's website. + * @param socialMediaUrls - Object containing social media URLs for the community. * @param facebook - Facebook URL. - * @param instagram - Instagram URL + * @param instagram - Instagram URL. * @param twitter - Twitter URL. * @param linkedIn - LinkedIn URL. * @param gitHub - GitHub URL. * @param youTube - YouTube URL. * @param slack - Slack URL. * @param reddit - Reddit URL. - * @param websiteLink - Community website URL. - * @param name - Community name. */ - const communitySchema = new Schema({ name: { type: String, @@ -76,6 +75,10 @@ const communitySchema = new Schema({ }, }); +/** + * Retrieves or creates the Mongoose model for Community. + * Prevents Mongoose OverwriteModelError during testing. + */ const communityModel = (): Model => model("Community", communitySchema); diff --git a/src/models/DirectChat.ts b/src/models/DirectChat.ts index 2f08a1e7a9..3715e0f687 100644 --- a/src/models/DirectChat.ts +++ b/src/models/DirectChat.ts @@ -5,7 +5,7 @@ import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** - * This is an interface representing a document for direct chat in the database(MongoDB). + * Interface representing a document for direct chat in MongoDB. */ export interface InterfaceDirectChat { _id: Types.ObjectId; @@ -17,15 +17,16 @@ export interface InterfaceDirectChat { createdAt: Date; updatedAt: Date; } + /** - * This is the Structure of the direct chat. - * @param users - Users of the chat - * @param messages - Messages - * @param creatorId - Creator of the chat, ref to `User` model - * @param organization - Organization - * @param status - whether the chat is active, blocked or deleted. - * @param createdAt - Timestamp of chat creation - * @param updatedAt - Timestamp of chat updation + * Mongoose schema for a direct chat. + * @param users - Users participating in the chat. + * @param messages - Messages in the chat. + * @param creatorId - Creator of the chat, reference to `User` model. + * @param organization - Organization associated with the chat, reference to `Organization` model. + * @param status - Status of the chat (ACTIVE, BLOCKED, DELETED). + * @param createdAt - Timestamp of chat creation. + * @param updatedAt - Timestamp of chat update. */ const directChatSchema = new Schema( { @@ -47,11 +48,6 @@ const directChatSchema = new Schema( ref: "User", required: true, }, - organization: { - type: Schema.Types.ObjectId, - ref: "Organization", - required: true, - }, status: { type: String, required: true, @@ -64,8 +60,13 @@ const directChatSchema = new Schema( }, ); +// Add logging middleware for directChatSchema createLoggingMiddleware(directChatSchema, "DirectChat"); +/** + * Retrieves or creates the Mongoose model for DirectChat. + * Prevents Mongoose OverwriteModelError during testing. + */ const directChatModel = (): Model => model("DirectChat", directChatSchema); diff --git a/src/models/DirectChatMessage.ts b/src/models/DirectChatMessage.ts index 54ca87ee8d..a31dcf809b 100644 --- a/src/models/DirectChatMessage.ts +++ b/src/models/DirectChatMessage.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceDirectChat } from "./DirectChat"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface representing a document for a direct chat message in the database(MongoDB). + * Represents a document for a direct chat message in the MongoDB database. */ export interface InterfaceDirectChatMessage { _id: Types.ObjectId; @@ -16,15 +17,16 @@ export interface InterfaceDirectChatMessage { createdAt: Date; updatedAt: Date; } + /** - * This is the Structure of the Direct chat Message - * @param directChatMessageBelongsTo - To whom the direct chat messages belong - * @param sender - Sender - * @param receiver - Receiver - * @param createdAt - Timestamp when the message was created - * @param updatedAt - Timestamp when the message was updated - * @param messageContent - Message content - * @param status - whether the message is active, blocked or deleted + * Mongoose schema definition for a direct chat message document. + * @param directChatMessageBelongsTo - Reference to the direct chat session to which the message belongs. + * @param sender - Reference to the user who sent the message. + * @param receiver - Reference to the user who received the message. + * @param messageContent - Content of the direct chat message. + * @param status - Status of the message (ACTIVE, BLOCKED, DELETED). + * @param createdAt - Date when the direct chat message was created. + * @param updatedAt - Date when the direct chat message was last updated. */ const directChatMessageSchema = new Schema( { @@ -55,12 +57,16 @@ const directChatMessageSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Adds createdAt and updatedAt automatically }, ); +// Apply logging middleware to the schema createLoggingMiddleware(directChatMessageSchema, "DirectChatMessage"); +/** + * Returns the Mongoose Model for DirectChatMessage to prevent OverwriteModelError. + */ const directChatMessageModel = (): Model => model( "DirectChatMessage", diff --git a/src/models/Donation.ts b/src/models/Donation.ts index ce94d0cf86..f63bbb34f0 100644 --- a/src/models/Donation.ts +++ b/src/models/Donation.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface representing a document for a donation in the database(MongoDB). + * Interface representing a document for a donation in MongoDB. */ export interface InterfaceDonation { userId: Types.ObjectId | string; @@ -14,16 +15,17 @@ export interface InterfaceDonation { createdAt: Date; updatedAt: Date; } + /** - * This is the Structure of the Donation - * @param userId - User-id - * @param orgId - Organization-id - * @param nameOfOrg - Name of the organization - * @param payPalId - PayPalId - * @param nameOfUser - Name of the user - * @param amount - Amount of the donation - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation + * Mongoose schema for a donation. + * @param userId - ID of the user making the donation. + * @param orgId - ID of the organization receiving the donation. + * @param nameOfOrg - Name of the organization. + * @param payPalId - PayPal ID used for the donation. + * @param nameOfUser - Name of the user making the donation. + * @param amount - Amount of the donation. + * @param createdAt - Timestamp of donation creation. + * @param updatedAt - Timestamp of donation update. */ const donationSchema = new Schema( { @@ -57,8 +59,12 @@ const donationSchema = new Schema( }, ); +// Add logging middleware for donationSchema createLoggingMiddleware(donationSchema, "Donation"); +/** + * Retrieves or creates the Mongoose model for Donation. + */ const donationModel = (): Model => model("Donation", donationSchema); diff --git a/src/models/EncodedImage.ts b/src/models/EncodedImage.ts index 23dcbe7dfd..dbeeb3dd9b 100644 --- a/src/models/EncodedImage.ts +++ b/src/models/EncodedImage.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Encoded Image. + * Represents a document for an encoded image in the MongoDB database. */ export interface InterfaceEncodedImage { _id: Types.ObjectId; @@ -10,11 +11,12 @@ export interface InterfaceEncodedImage { content: string; numberOfUses: number; } + /** - * This describes the schema for a `encodedImage` that corresponds to `InterfaceEncodedImage` document. - * @param fileName - File name. - * @param content - Content. - * @param numberOfUses - Number of Uses. + * Mongoose schema definition for an encoded image document. + * @param fileName - File name of the encoded image. + * @param content - Content of the encoded image. + * @param numberOfUses - Number of times the encoded image has been used. */ const encodedImageSchema = new Schema({ fileName: { @@ -32,6 +34,7 @@ const encodedImageSchema = new Schema({ }, }); +// Apply logging middleware to the schema createLoggingMiddleware(encodedImageSchema, "EncodedImage"); const encodedImageModel = (): Model => diff --git a/src/models/EncodedVideo.ts b/src/models/EncodedVideo.ts index 4cdf2f2ff7..eda2f21685 100644 --- a/src/models/EncodedVideo.ts +++ b/src/models/EncodedVideo.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Encoded Video. + * Interface representing a document for an encoded video in MongoDB. */ export interface InterfaceEncodedVideo { _id: Types.ObjectId; @@ -10,11 +11,12 @@ export interface InterfaceEncodedVideo { content: string; numberOfUses: number; } + /** - * This describes the schema for a `encodedVideo` that corresponds to `InterfaceEncodedVideo` document. - * @param fileName - File name. - * @param content - Content. - * @param numberOfUses - Number of Uses. + * Mongoose schema for an encoded video. + * @param fileName - Name of the file for the encoded video. + * @param content - Content of the encoded video. + * @param numberOfUses - Number of times the encoded video has been used. */ const encodedVideoSchema = new Schema({ fileName: { @@ -28,12 +30,16 @@ const encodedVideoSchema = new Schema({ numberOfUses: { type: Number, required: true, - default: 1, + default: 1, // Default value set to 1 when a new document is created. }, }); +// Add logging middleware for encodedVideoSchema createLoggingMiddleware(encodedVideoSchema, "EncodedVideo"); +/** + * Retrieves or creates the Mongoose model for EncodedVideo. + */ const encodedVideoModel = (): Model => model("EncodedVideo", encodedVideoSchema); diff --git a/src/models/Event.ts b/src/models/Event.ts index 55ae7fe476..2a30077aad 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -8,7 +8,7 @@ import type { InterfaceRecurrenceRule } from "./RecurrenceRule"; import type { InterfaceAgendaItem } from "./AgendaItem"; /** - * This is an interface representing a document for an event in the database(MongoDB). + * Represents a document for an event in the MongoDB database. */ export interface InterfaceEvent { _id: Types.ObjectId; @@ -42,35 +42,33 @@ export interface InterfaceEvent { } /** - * This is the Structure of the Event - * @param admins - Admins - * @param allDay - Is the event occuring all day - * @param attendees - Attendees - * @param baseRecurringEventId - Id of the true recurring event used for generating this instance - * @param createdAt - Timestamp of event creation - * @param creatorId - Creator of the event - * @param description - Description of the event - * @param endDate - End date - * @param endTime - End Time - * @param images -Event Flyer - * @param isBaseRecurringEvent - Is the event a true recurring event that is used for generating new instances - * @param isPublic - Is the event public - * @param isRecurringEventException - Is the event an exception to the recurring pattern it was following - * @param isRegisterable - Is the event Registrable - * @param latitude - Latitude - * @param location - Location of the event - * @param longitude - Longitude - * @param organization - Organization - * @param recurrance - Periodicity of recurrance of the event - * @param recurrenceRuleId - Id of the recurrence rule document containing the recurrence pattern for the event - * @param recurring - Is the event recurring - * @param startDate - Start Date - * @param startTime - Start Time - * @param title - Title of the event - * @param updatedAt - Timestamp of event updation - * @param volunteerGroups - event volunteer groups for the event + * Mongoose schema definition for an event document. + * @param title - Title of the event. + * @param description - Description of the event. + * @param attendees - Optional attendees information. + * @param images - Array of image URLs associated with the event. + * @param location - Optional location of the event. + * @param latitude - Latitude coordinate of the event location. + * @param longitude - Longitude coordinate of the event location. + * @param recurring - Indicates if the event is recurring. + * @param isRecurringEventException - Indicates if the event is an exception to a recurring pattern. + * @param isBaseRecurringEvent - Indicates if the event is a base recurring event. + * @param recurrenceRuleId - Reference to the recurrence rule for recurring events. + * @param baseRecurringEventId - Reference to the base recurring event for generated instances. + * @param allDay - Indicates if the event occurs throughout the entire day. + * @param startDate - Start date of the event. + * @param endDate - Optional end date of the event. + * @param startTime - Optional start time of the event. + * @param endTime - Optional end time of the event. + * @param isPublic - Indicates if the event is public. + * @param isRegisterable - Indicates if the event is registerable. + * @param creatorId - Reference to the user who created the event. + * @param admins - Array of admins for the event. + * @param organization - Reference to the organization hosting the event. + * @param volunteerGroups - Array of volunteer groups associated with the event. + * @param createdAt - Timestamp of when the event was created. + * @param updatedAt - Timestamp of when the event was last updated. */ - const eventSchema = new Schema( { title: { @@ -194,6 +192,7 @@ const eventSchema = new Schema( }, ); +// Apply logging middleware to the schema createLoggingMiddleware(eventSchema, "Event"); const eventModel = (): Model => diff --git a/src/models/EventAttendee.ts b/src/models/EventAttendee.ts index 7d984a3b6d..6ff1cec5d9 100644 --- a/src/models/EventAttendee.ts +++ b/src/models/EventAttendee.ts @@ -6,6 +6,9 @@ import type { InterfaceCheckIn } from "./CheckIn"; import type { InterfaceCheckOut } from "./CheckOut"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Interface representing a document for an event attendee in MongoDB. + */ export interface InterfaceEventAttendee { _id: Schema.Types.ObjectId; userId: PopulatedDoc; @@ -18,6 +21,17 @@ export interface InterfaceEventAttendee { isCheckedOut: boolean; } +/** + * Mongoose schema for an event attendee. + * @param userId - Reference to the user attending the event. + * @param eventId - Reference to the event the attendee is associated with. + * @param checkInId - Reference to the check-in record if checked in, or null. + * @param checkOutId - Reference to the check-out record if checked out, or null. + * @param isInvited - Indicates if the attendee is invited to the event. + * @param isRegistered - Indicates if the attendee is registered for the event. + * @param isCheckedIn - Indicates if the attendee is checked in to the event. + * @param isCheckedOut - Indicates if the attendee is checked out from the event. + */ const eventAttendeeSchema = new Schema({ userId: { type: Schema.Types.ObjectId, @@ -67,8 +81,10 @@ const eventAttendeeSchema = new Schema({ }, }); +// Ensure uniqueness of combinations of userId and eventId eventAttendeeSchema.index({ userId: 1, eventId: 1 }, { unique: true }); +// Add logging middleware for eventAttendeeSchema createLoggingMiddleware(eventAttendeeSchema, "EventAttendee"); const eventAttendeeModel = (): Model => diff --git a/src/models/EventVolunteer.ts b/src/models/EventVolunteer.ts index f50c5d62db..0600f77b4c 100644 --- a/src/models/EventVolunteer.ts +++ b/src/models/EventVolunteer.ts @@ -5,6 +5,10 @@ import type { InterfaceEvent } from "./Event"; import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; +/** + * Represents a document for an event volunteer in the MongoDB database. + * This interface defines the structure and types of data that an event volunteer document will hold. + */ export interface InterfaceEventVolunteer { _id: Types.ObjectId; createdAt: Date; @@ -18,6 +22,20 @@ export interface InterfaceEventVolunteer { userId: PopulatedDoc; } +/** + * Mongoose schema definition for an event volunteer document. + * This schema defines how the data will be stored in the MongoDB database. + * + * @param creatorId - Reference to the user who created the event volunteer entry. + * @param eventId - Reference to the event for which the user volunteers. + * @param groupId - Reference to the volunteer group associated with the event. + * @param response - Response status of the volunteer ("YES", "NO", null). + * @param isAssigned - Indicates if the volunteer is assigned to a specific role. + * @param isInvited - Indicates if the volunteer has been invited to participate. + * @param userId - Reference to the user who is volunteering for the event. + * @param createdAt - Timestamp of when the event volunteer document was created. + * @param updatedAt - Timestamp of when the event volunteer document was last updated. + */ const eventVolunteerSchema = new Schema( { creatorId: { @@ -50,16 +68,25 @@ const eventVolunteerSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); -// Enable logging on changes in EventVolunteer collection +// Apply logging middleware to the schema createLoggingMiddleware(eventVolunteerSchema, "EventVolunteer"); +/** + * Creates a Mongoose model for the event volunteer schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The EventVolunteer model. + */ const eventVolunteerModel = (): Model => model("EventVolunteer", eventVolunteerSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * Export the EventVolunteer model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const EventVolunteer = (models.EventVolunteer || eventVolunteerModel()) as ReturnType; diff --git a/src/models/EventVolunteerGroup.ts b/src/models/EventVolunteerGroup.ts index b93cd17929..8f8fc5072e 100644 --- a/src/models/EventVolunteerGroup.ts +++ b/src/models/EventVolunteerGroup.ts @@ -5,6 +5,10 @@ import type { InterfaceEvent } from "./Event"; import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceEventVolunteer } from "./EventVolunteer"; +/** + * Represents a document for an event volunteer group in the MongoDB database. + * This interface defines the structure and types of data that an event volunteer group document will hold. + */ export interface InterfaceEventVolunteerGroup { _id: Types.ObjectId; createdAt: Date; @@ -17,6 +21,19 @@ export interface InterfaceEventVolunteerGroup { volunteersRequired?: number; } +/** + * Mongoose schema definition for an event volunteer group document. + * This schema defines how the data will be stored in the MongoDB database. + * + * @param creatorId - Reference to the user who created the event volunteer group entry. + * @param eventId - Reference to the event for which the volunteer group is created. + * @param leaderId - Reference to the leader of the volunteer group. + * @param name - Name of the volunteer group. + * @param volunteers - List of volunteers in the group. + * @param volunteersRequired - Number of volunteers required for the group (optional). + * @param createdAt - Timestamp of when the event volunteer group document was created. + * @param updatedAt - Timestamp of when the event volunteer group document was last updated. + */ const eventVolunteerGroupSchema = new Schema( { creatorId: { @@ -50,19 +67,28 @@ const eventVolunteerGroupSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); -// Enable logging on changes in EventVolunteer collection +// Enable logging on changes in EventVolunteerGroup collection createLoggingMiddleware(eventVolunteerGroupSchema, "EventVolunteerGroup"); +/** + * Creates a Mongoose model for the event volunteer group schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The EventVolunteerGroup model. + */ const eventVolunteerGroupModel = (): Model => model( "EventVolunteerGroup", eventVolunteerGroupSchema, ); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * Export the EventVolunteerGroup model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const EventVolunteerGroup = (models.EventVolunteerGroup || eventVolunteerGroupModel()) as ReturnType; diff --git a/src/models/Feedback.ts b/src/models/Feedback.ts index 6a8d00faa7..c8c3ecbeb8 100644 --- a/src/models/Feedback.ts +++ b/src/models/Feedback.ts @@ -3,6 +3,10 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceEvent } from "./Event"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Represents a document for feedback in the MongoDB database. + * This interface defines the structure and types of data that a feedback document will hold. + */ export interface InterfaceFeedback { _id: Types.ObjectId; eventId: PopulatedDoc; @@ -12,6 +16,16 @@ export interface InterfaceFeedback { updatedAt: Date; } +/** + * Mongoose schema definition for a feedback document. + * This schema defines how the data will be stored in the MongoDB database. + * + * @param eventId - Reference to the event for which the feedback is given. + * @param rating - The rating given for the event. + * @param review - The review text provided for the event (optional). + * @param createdAt - Timestamp of when the feedback document was created. + * @param updatedAt - Timestamp of when the feedback document was last updated. + */ const feedbackSchema = new Schema( { eventId: { @@ -31,21 +45,31 @@ const feedbackSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); -// We will also create an index here for faster database querying +// Create an index on the eventId field for faster database querying feedbackSchema.index({ eventId: 1, }); +// Enable logging on changes in the Feedback collection createLoggingMiddleware(feedbackSchema, "Feedback"); +/** + * Creates a Mongoose model for the feedback schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The Feedback model. + */ const feedbackModel = (): Model => model("Feedback", feedbackSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * Export the Feedback model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const Feedback = (models.Feedback || feedbackModel()) as ReturnType< typeof feedbackModel >; diff --git a/src/models/File.ts b/src/models/File.ts index 19fee53c83..bd8550f2dc 100644 --- a/src/models/File.ts +++ b/src/models/File.ts @@ -16,23 +16,25 @@ export interface InterfaceFile { createdAt: Date; updatedAt: Date; } + /** - * This is the structure of a file - * @param name - Name - * @param url - URL - * @param size - Size - * @param secret - Secret - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation - * @param contentType - Content Type - * @param status - Status + * Mongoose schema for a file. + * Defines the structure of the file document stored in MongoDB. + * @param name - The name of the file. + * @param url - The URL where the file is stored. + * @param size - The size of the file in bytes. + * @param secret - A secret key associated with the file. + * @param contentType - The MIME type of the file. + * @param status - The status of the file (e.g., ACTIVE, BLOCKED, DELETED). + * @param createdAt - The date and time when the file was created. + * @param updatedAt - The date and time when the file was last updated. */ const fileSchema = new Schema( { name: { type: String, required: true, - default: uuidv4(), + default: uuidv4(), // Generates a unique identifier for the name by default }, url: { type: String, @@ -55,16 +57,26 @@ const fileSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for fileSchema createLoggingMiddleware(fileSchema, "File"); +/** + * Function to retrieve or create the Mongoose model for the File. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the File. + */ const fileModel = (): Model => model("File", fileSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the File. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const File = (models.File || fileModel()) as ReturnType< typeof fileModel >; diff --git a/src/models/Fund.ts b/src/models/Fund.ts index e0d088fca2..6c4cc93bc6 100644 --- a/src/models/Fund.ts +++ b/src/models/Fund.ts @@ -1,10 +1,11 @@ import type { Model, PopulatedDoc, Types } from "mongoose"; - import { Schema, model, models } from "mongoose"; import type { InterfaceFundraisingCampaign } from "./FundraisingCampaign"; import type { InterfaceUser } from "./User"; + /** - * This is an interface representing a document for fund in the database(MongoDB). + * This is an interface representing a document for a fund in the database (MongoDB). + * This interface defines the structure and types of data that a fund document will hold. */ export interface InterfaceFund { _id: Types.ObjectId; @@ -21,18 +22,19 @@ export interface InterfaceFund { } /** - * This is the structure of a file - * @param organizationId - Organization ID to which the fund belongs - * @param name - Name of the fund - * @param refrenceNumber - Reference number of the fund - * @param taxDeductible - Whether the fund is tax deductible - * @param isDefault - Whether the fund is default - * @param isArchived - Whether the fund is archived - * @param campaign - Campaigns associated with the fund - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation - * @param creatorId - User who created the fund, refer to `User` model. + * Mongoose schema definition for a fund document. + * This schema defines how the data will be stored in the MongoDB database. * + * @param organizationId - Organization ID to which the fund belongs. + * @param name - Name of the fund. + * @param refrenceNumber - Reference number of the fund. + * @param taxDeductible - Indicates whether the fund is tax deductible. + * @param isDefault - Indicates whether the fund is the default fund. + * @param isArchived - Indicates whether the fund is archived. + * @param creatorId - Reference to the user who created the fund (refers to the `User` model). + * @param campaigns - Campaigns associated with the fund. + * @param createdAt - Timestamp of when the fund document was created. + * @param updatedAt - Timestamp of when the fund document was last updated. */ const fundSchema = new Schema( { @@ -68,17 +70,28 @@ const fundSchema = new Schema( campaigns: [ { type: Schema.Types.ObjectId, - ref: "FundCampaign", + ref: "FundraisingCampaign", }, ], }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); + +/** + * Creates a Mongoose model for the fund schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The Fund model. + */ const fundModel = (): Model => model("Fund", fundSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. + +/** + * Export the Fund model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const Fund = (models.Fund || fundModel()) as ReturnType< typeof fundModel >; diff --git a/src/models/FundraisingCampaign.ts b/src/models/FundraisingCampaign.ts index 380b1fb3f3..33af442b4a 100644 --- a/src/models/FundraisingCampaign.ts +++ b/src/models/FundraisingCampaign.ts @@ -1,8 +1,13 @@ import type { Model, PopulatedDoc, Types } from "mongoose"; - import { Schema, model, models } from "mongoose"; import type { InterfaceFund } from "./Fund"; import type { InterfaceFundraisingCampaignPledges } from "./FundraisingCampaignPledge"; +import { createLoggingMiddleware } from "../libraries/dbLogger"; + +/** + * Enum for currency types with their respective codes. + * This enum lists all the possible currency codes that can be used in the system. + */ export enum CurrencyType { AED = "AED", // United Arab Emirates Dirham AFN = "AFN", // Afghan Afghani @@ -169,18 +174,6 @@ export enum CurrencyType { ZWD = "ZWD", // Zimbabwean Dollar } -/** - * This is the structure of a file - * @param fund - parent fund to which campaign belongs - * @param name - Name of the campaign - * @param startDate - Start date of the campaign - * @param endDate - End date of the campaign - * @param fundingGoal - Goal of the campaign - * @param currency - Currency of the campaign - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation - * - */ export interface InterfaceFundraisingCampaign { _id: Types.ObjectId; fundId: PopulatedDoc; @@ -193,6 +186,19 @@ export interface InterfaceFundraisingCampaign { createdAt: Date; updatedAt: Date; } + +/** + * Mongoose schema definition for a fundraising campaign document. + * This schema defines how the data will be stored in the MongoDB database. + * + * @param fundId - Reference to the parent fund. + * @param name - Name of the fundraising campaign. + * @param startDate - Start date of the fundraising campaign. + * @param endDate - End date of the fundraising campaign. + * @param fundingGoal - Financial goal of the fundraising campaign. + * @param currency - Currency in which the funding goal is specified. + * @param pledges - List of pledges associated with the fundraising campaign. + */ const fundraisingCampaignSchema = new Schema( { fundId: { @@ -219,7 +225,7 @@ const fundraisingCampaignSchema = new Schema( currency: { type: String, required: true, - enum: Object.values(CurrencyType), + enum: Object.values(CurrencyType), // Must be one of the defined currency types }, pledges: [ { @@ -229,14 +235,31 @@ const fundraisingCampaignSchema = new Schema( ], }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); + +/** + * Adds logging middleware to the fundraising campaign schema. + * This middleware logs changes to fundraising campaign documents. + */ +createLoggingMiddleware(fundraisingCampaignSchema, "FundRaisingCampaign"); + +/** + * Creates a Mongoose model for the fundraising campaign schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The FundraisingCampaign model. + */ const fundraisingCampaignModel = (): Model => model( "FundraisingCampaign", fundraisingCampaignSchema, ); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. + +/** + * Export the FundraisingCampaign model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const FundraisingCampaign = (models.FundraisingCampaign || fundraisingCampaignModel()) as ReturnType; diff --git a/src/models/FundraisingCampaignPledge.ts b/src/models/FundraisingCampaignPledge.ts index 2e4b066db8..ef443c575f 100644 --- a/src/models/FundraisingCampaignPledge.ts +++ b/src/models/FundraisingCampaignPledge.ts @@ -6,6 +6,9 @@ import { } from "./FundraisingCampaign"; import { type InterfaceUser } from "./User"; +/** + * Interface representing a document for a fundraising campaign pledge in the database (MongoDB). + */ export interface InterfaceFundraisingCampaignPledges { _id: Types.ObjectId; campaigns: PopulatedDoc[]; @@ -17,6 +20,19 @@ export interface InterfaceFundraisingCampaignPledges { createdAt: Date; updatedAt: Date; } + +/** + * Mongoose schema for a fundraising campaign pledge. + * Defines the structure of the pledge document stored in MongoDB. + * @param campaigns - The fundraising campaigns associated with the pledge. + * @param users - The users who made the pledge. + * @param startDate - The start date of the pledge. + * @param endDate - The end date of the pledge. + * @param amount - The amount pledged. + * @param currency - The currency type of the amount pledged. + * @param createdAt - The date and time when the pledge was created. + * @param updatedAt - The date and time when the pledge was last updated. + */ const fundraisingCampaignPledgeSchema = new Schema( { campaigns: [ @@ -50,9 +66,15 @@ const fundraisingCampaignPledgeSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); + +/** + * Function to retrieve or create the Mongoose model for the FundraisingCampaignPledge. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the FundraisingCampaignPledge. + */ const fundraisingCampaignPledgeModel = (): Model => model( @@ -60,7 +82,11 @@ const fundraisingCampaignPledgeModel = fundraisingCampaignPledgeSchema, ); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the FundraisingCampaignPledge. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const FundraisingCampaignPledge = (models.FundraisingCampaignPledge || fundraisingCampaignPledgeModel()) as ReturnType< typeof fundraisingCampaignPledgeModel diff --git a/src/models/Group.ts b/src/models/Group.ts index fd1acc6caf..e3d0256a7c 100644 --- a/src/models/Group.ts +++ b/src/models/Group.ts @@ -4,7 +4,7 @@ import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** - * This is an interface representing a document for a group in the database(MongoDB). + * Interface representing a document for a group in the database (MongoDB). */ export interface InterfaceGroup { _id: Types.ObjectId; @@ -17,13 +17,15 @@ export interface InterfaceGroup { updatedAt: Date; } /** - * This is the structure of a group - * @param title - Title - * @param description - Description - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation - * @param status - Status - * @param admins - Admins + * Mongoose schema for a group. + * Defines the structure of the group document stored in MongoDB. + * @param title - The title of the group. + * @param description - A description of the group. + * @param organization - The organization the group belongs to. + * @param status - The status of the group (e.g., ACTIVE, BLOCKED, DELETED). + * @param admins - The administrators of the group. + * @param createdAt - The date and time when the group was created. + * @param updatedAt - The date and time when the group was last updated. */ const groupSchema = new Schema( { @@ -54,16 +56,26 @@ const groupSchema = new Schema( ], }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for groupSchema createLoggingMiddleware(groupSchema, "Group"); +/** + * Function to retrieve or create the Mongoose model for the Group. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Group. + */ const groupModel = (): Model => model("Group", groupSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Group. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const Group = (models.Group || groupModel()) as ReturnType< typeof groupModel >; diff --git a/src/models/GroupChat.ts b/src/models/GroupChat.ts index 6e654ef2e2..3ecf20f70d 100644 --- a/src/models/GroupChat.ts +++ b/src/models/GroupChat.ts @@ -4,8 +4,9 @@ import type { InterfaceGroupChatMessage } from "./GroupChatMessage"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface representing a document for a group chat in the database(MongoDB). + * Interface representing a document for a group chat in the database (MongoDB). */ export interface InterfaceGroupChat { _id: Types.ObjectId; @@ -19,15 +20,17 @@ export interface InterfaceGroupChat { status: string; } /** - * This is the structure of a group chat - * @param title - Title - * @param users - Users of the chat - * @param messages - Message of the chat - * @param creatorId - Creator of the chat + * Mongoose schema definition for a group chat document. + * Defines how group chat data will be stored in MongoDB. + * + * @param title - Title of the group chat. + * @param users - Users participating in the group chat. + * @param messages - Messages sent in the group chat. + * @param creatorId - Creator of the group chat. * @param createdAt - Timestamp of creation * @param updatedAt - Timestamp of updation - * @param organization - Organization - * @param status - Status + * @param organization - Organization associated with the group chat. + * @param status - Status of the group chat. */ const groupChatSchema = new Schema( { @@ -39,7 +42,7 @@ const groupChatSchema = new Schema( { type: Schema.Types.ObjectId, ref: "User", - required: true, + required: true, // At least one user is required }, ], messages: [ @@ -61,21 +64,34 @@ const groupChatSchema = new Schema( status: { type: String, required: true, - enum: ["ACTIVE", "BLOCKED", "DELETED"], + enum: ["ACTIVE", "BLOCKED", "DELETED"], // Status must be one of these values default: "ACTIVE", }, }, { - timestamps: true, + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields }, ); +/** + * Adds logging middleware to the group chat schema. + * Middleware logs changes to group chat documents. + */ createLoggingMiddleware(groupChatSchema, "GroupChat"); +/** + * Creates a Mongoose model for the group chat schema. + * Ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The GroupChat model. + */ const groupChatModel = (): Model => model("GroupChat", groupChatSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * Export the GroupChat model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ export const GroupChat = (models.GroupChat || groupChatModel()) as ReturnType< typeof groupChatModel >; diff --git a/src/models/GroupChatMessage.ts b/src/models/GroupChatMessage.ts index 639196e153..f3b583bb2b 100644 --- a/src/models/GroupChatMessage.ts +++ b/src/models/GroupChatMessage.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceGroupChat } from "./GroupChat"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Group Chat Message. + * Interface representing a document for a group chat message in the database (MongoDB). */ export interface InterfaceGroupChatMessage { _id: Types.ObjectId; @@ -15,14 +16,16 @@ export interface InterfaceGroupChatMessage { messageContent: string; status: string; } + /** - * This represents the schema for a `GroupChatMessage`. - * @param groupChatMessageBelongsTo - This is the association referring to the `GroupChat` model. - * @param sender - Sender of the message. - * @param createdAt - Time stamp of data creation. - * @param updatedAt - Time stamp of data updation. - * @param messageContent - Content of the message. - * @param status - Status + * Mongoose schema for a group chat message. + * Defines the structure of the group chat message document stored in MongoDB. + * @param groupChatMessageBelongsTo - The association referring to the GroupChat model. + * @param sender - The sender of the message. + * @param messageContent - The content of the message. + * @param status - The status of the message (e.g., ACTIVE, BLOCKED, DELETED). + * @param createdAt - The date and time when the message was created. + * @param updatedAt - The date and time when the message was last updated. */ const groupChatMessageSchema = new Schema( { @@ -48,15 +51,25 @@ const groupChatMessageSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for groupChatMessageSchema createLoggingMiddleware(groupChatMessageSchema, "GroupChatMessage"); +/** + * Function to retrieve or create the Mongoose model for the GroupChatMessage. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the GroupChatMessage. + */ const groupChatMessageModel = (): Model => model("GroupChatMessage", groupChatMessageSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the GroupChatMessage. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const GroupChatMessage = (models.GroupChatMessage || groupChatMessageModel()) as ReturnType; diff --git a/src/models/ImageHash.ts b/src/models/ImageHash.ts index 2dff1cb94e..54a22cbc0c 100644 --- a/src/models/ImageHash.ts +++ b/src/models/ImageHash.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Image Hash. + * Interface representing a document for an image hash in the database (MongoDB). */ export interface InterfaceImageHash { _id: Types.ObjectId; @@ -11,12 +12,14 @@ export interface InterfaceImageHash { numberOfUses: number; status: string; } + /** - * This represents the schema for an `ImageHash`. - * @param hashValue - Hash value of an image. `type: String` - * @param fileName - Image file name. - * @param numberOfUses - Number of times used. - * @param status - Status. + * Mongoose schema for an image hash. + * Defines the structure of the image hash document stored in MongoDB. + * @param hashValue - The hash value of the image. + * @param fileName - The file name of the image. + * @param numberOfUses - The number of times the image hash has been used. + * @param status - The status of the image hash (e.g., ACTIVE, BLOCKED, DELETED). */ const imageHashSchema = new Schema({ hashValue: { @@ -40,12 +43,22 @@ const imageHashSchema = new Schema({ }, }); +// Add logging middleware for imageHashSchema createLoggingMiddleware(imageHashSchema, "ImageHash"); +/** + * Function to retrieve or create the Mongoose model for the ImageHash. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the ImageHash. + */ const imageHashModel = (): Model => model("ImageHash", imageHashSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the ImageHash. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const ImageHash = (models.ImageHash || imageHashModel()) as ReturnType< typeof imageHashModel >; diff --git a/src/models/Language.ts b/src/models/Language.ts index a80a5e7f37..671aed737c 100644 --- a/src/models/Language.ts +++ b/src/models/Language.ts @@ -1,8 +1,9 @@ import type { Types, Document, PopulatedDoc, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database document. + * Interface representing a Language Model document in the database (MongoDB). */ export interface InterfaceLanguageModel { lang_code: string; @@ -10,13 +11,14 @@ export interface InterfaceLanguageModel { verified: boolean; createdAt: Date; } + /** - * This schema defines the structure of a Language Model that corresponds to `InterfaceLanguageModel` document. - * which is utilised as an association in the 'languageSchema' schema. - * @param lang_code - Code of the language, for example: en for english. - * @param value - Value. - * @param verified - Language code is verified or not. - * @param createdAt - Time stamp of data creation. + * Mongoose schema for a Language Model. + * Defines the structure of the Language Model document stored in MongoDB. + * @param lang_code - The code of the language (e.g., "en" for English). + * @param value - The value associated with the language. + * @param verified - Indicates if the language code is verified. + * @param createdAt - The date and time when the language model was created. */ const languageModelSchema = new Schema({ lang_code: { @@ -41,8 +43,9 @@ const languageModelSchema = new Schema({ default: Date.now, }, }); + /** - * This is an interface that represents a database(MongoDB) document for Language. + * Interface representing a Language document in the database (MongoDB). */ export interface InterfaceLanguage { _id: Types.ObjectId; @@ -50,11 +53,13 @@ export interface InterfaceLanguage { translation: PopulatedDoc[]; createdAt: Date; } + /** - * This is the structure of a Language Schema that corresponds to `InterfaceLanguage` document. - * @param en - Code for english language. - * @param translation - Association that refers to `LangModel` Schema. - * @param createdAt - Time stamp of data creation. + * Mongoose schema for a Language. + * Defines the structure of the Language document stored in MongoDB. + * @param en - The code for the English language. + * @param translation - An array of Language Model documents associated with this language. + * @param createdAt - The date and time when the language document was created. */ const languageSchema = new Schema({ en: { @@ -71,12 +76,22 @@ const languageSchema = new Schema({ }, }); +// Add logging middleware for languageSchema createLoggingMiddleware(languageSchema, "Language"); +/** + * Function to retrieve or create the Mongoose model for the Language. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Language. + */ const languageModel = (): Model => model("Language", languageSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Language. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const Language = (models.Language || languageModel()) as ReturnType< typeof languageModel >; diff --git a/src/models/MembershipRequest.ts b/src/models/MembershipRequest.ts index 97455b22ed..86b46c8d1f 100644 --- a/src/models/MembershipRequest.ts +++ b/src/models/MembershipRequest.ts @@ -4,7 +4,7 @@ import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** - * This is an interface that represents a database(MongoDB) document for Membership Request. + * Represents a database document for Membership Request. */ export interface InterfaceMembershipRequest { _id: Types.ObjectId; @@ -12,11 +12,12 @@ export interface InterfaceMembershipRequest { user: PopulatedDoc | undefined; status: string; } + /** - * This describes the schema for a `MembershipRequest` that corresponds to `InterfaceMembershipRequest` document. - * @param organization - Organization data for which membership request is added. - * @param user - User data who requested membership for an organization. - * @param status - Status. + * Mongoose schema definition for Membership Request documents. + * @param organization - The ID of the organization for which membership is requested. + * @param user - The ID of the user who requested membership. + * @param status - The current status of the membership request. */ const membershipRequestSchema = new Schema({ organization: { @@ -37,14 +38,18 @@ const membershipRequestSchema = new Schema({ }, }); +// Adding logging middleware for Membership Request schema. createLoggingMiddleware(membershipRequestSchema, "MembershipRequest"); +/** + * Mongoose model for Membership Request documents. + */ const membershipRequestModel = (): Model => model( "MembershipRequest", membershipRequestSchema, ); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +// Exporting the Membership Request model while preventing Mongoose OverwriteModelError during tests. export const MembershipRequest = (models.MembershipRequest || membershipRequestModel()) as ReturnType; diff --git a/src/models/Message.ts b/src/models/Message.ts index 53a2addaae..045a5a7c84 100644 --- a/src/models/Message.ts +++ b/src/models/Message.ts @@ -3,8 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceGroup } from "./Group"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Message. + * Interface representing a document for a message in the database (MongoDB). */ export interface InterfaceMessage { _id: Types.ObjectId; @@ -17,16 +18,18 @@ export interface InterfaceMessage { group: PopulatedDoc; status: string; } + /** - * This describes the schema for a `Message` that corresponds to `InterfaceMessage` document. - * @param text - Message content. - * @param imageUrl - Image URL attached in the message. - * @param videoUrl - Video URL attached in the message. - * @param createdAt - Timestamp of data creation. - * @param creatorId - Message Sender(User), referring to `User` model. - * @param updatedAt - Timestamp of data updation - * @param group - group data, referring to `Group` model. - * @param status - Status. + * Mongoose schema for a Message. + * Defines the structure of the Message document stored in MongoDB. + * @param text - The content of the message. + * @param imageUrl - Optional URL of an image attached to the message. + * @param videoUrl - Optional URL of a video attached to the message. + * @param creatorId - Reference to the User who created the message. + * @param group - Reference to the Group to which the message belongs. + * @param status - The status of the message (e.g., ACTIVE, BLOCKED, DELETED). + * @param createdAt - The date and time when the message was created. + * @param updatedAt - The date and time when the message was last updated. */ const messageSchema = new Schema( { @@ -60,16 +63,26 @@ const messageSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for messageSchema createLoggingMiddleware(messageSchema, "Message"); +/** + * Function to retrieve or create the Mongoose model for the Message. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Message. + */ const messageModel = (): Model => model("Message", messageSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Message. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const Message = (models.Message || messageModel()) as ReturnType< typeof messageModel >; diff --git a/src/models/MessageChat.ts b/src/models/MessageChat.ts index 2e337321b9..7510ea8d1a 100644 --- a/src/models/MessageChat.ts +++ b/src/models/MessageChat.ts @@ -3,7 +3,7 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** - * This is an interface representing a document for a chat in the database(MongoDB). + * Interface representing a document for a chat in the database (MongoDB). */ export interface InterfaceMessageChat { _id: Types.ObjectId; @@ -15,13 +15,14 @@ export interface InterfaceMessageChat { updatedAt: Date; } /** - * This the structure of a chat - * @param message - Chat message - * @param languageBarrier - Boolean Type - * @param sender - Sender - * @param receiver - Receiver - * @param createdAt - Date when the chat was created - * @param updatedAt - Date when the chat was updated + * Mongoose schema for a Message Chat. + * Defines the structure of the Message Chat document stored in MongoDB. + * @param message - The content of the chat message. + * @param languageBarrier - Indicates if there's a language barrier in the chat. + * @param sender - Reference to the User who sent the chat message. + * @param receiver - Reference to the User who received the chat message. + * @param createdAt - The date and time when the chat was created. + * @param updatedAt - The date and time when the chat was last updated. */ const messageChatSchema = new Schema( { @@ -46,15 +47,25 @@ const messageChatSchema = new Schema( }, }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for messageChatSchema createLoggingMiddleware(messageChatSchema, "MessageChat"); +/** + * Function to retrieve or create the Mongoose model for the MessageChat. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the MessageChat. + */ const messageChatModel = (): Model => model("MessageChat", messageChatSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the MessageChat. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const MessageChat = (models.MessageChat || messageChatModel()) as ReturnType; diff --git a/src/models/Note.ts b/src/models/Note.ts index 8dcef37fbb..e6d1deed2f 100644 --- a/src/models/Note.ts +++ b/src/models/Note.ts @@ -2,6 +2,9 @@ import type { Model, PopulatedDoc, Types } from "mongoose"; import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; +/** + * Represents a note document in the database. + */ export interface InterfaceNote { _id: Types.ObjectId; // Unique identifier for the note. content: string; // Content of the note. @@ -12,6 +15,15 @@ export interface InterfaceNote { agendaItemId: Types.ObjectId; // Reference to the agenda item associated with the note. } +/** + * Mongoose schema definition for Note documents. + * @param content - The content of the note. + * @param createdBy - The ID of the user who created the note. + * @param updatedBy - Optional: The ID of the user who last updated the note. + * @param createdAt - The date when the note was created. + * @param updatedAt - Optional: The date when the note was last updated. + * @param agendaItemId - The ID of the agenda item associated with the note. + */ export const NoteSchema = new Schema({ content: { type: String, @@ -42,9 +54,13 @@ export const NoteSchema = new Schema({ }, }); +/** + * Mongoose model for Note documents. + */ const noteModel = (): Model => model("Note", NoteSchema); +// Exporting the Note model while preventing Mongoose OverwriteModelError during tests. export const NoteModel = (models.Note || noteModel()) as ReturnType< typeof noteModel >; diff --git a/src/models/Organization.ts b/src/models/Organization.ts index 226fdb4beb..e307d615c6 100644 --- a/src/models/Organization.ts +++ b/src/models/Organization.ts @@ -7,11 +7,10 @@ import type { InterfaceMessage } from "./Message"; import type { InterfaceOrganizationCustomField } from "./OrganizationCustomField"; import type { InterfacePost } from "./Post"; import type { InterfaceUser } from "./User"; - import type { InterfaceAdvertisement } from "./Advertisement"; /** - * This is an interface that represents a database(MongoDB) document for Organization. + * Interface representing a document for an Organization in the database (MongoDB). */ export interface InterfaceOrganization { _id: Types.ObjectId; @@ -46,24 +45,31 @@ export interface InterfaceOrganization { funds: PopulatedDoc[]; visibleInSearch: boolean; } + /** - * This describes the schema for a `Organization` that corresponds to `InterfaceOrganization` document. - * @param apiUrl - API URL. - * @param image - Organization image URL. - * @param name - Organization name. - * @param description - Organization description. - * @param address - Organization address, stored as an object. - * @param creatorId - Organization creator, referring to `User` model. - * @param status - Status. - * @param members - Collection of members, each object refer to `User` model. - * @param admins - Collection of organization admins, each object refer to `User` model. - * @param groupChats - Collection of group chats, each object refer to `Message` model. - * @param posts - Collection of Posts in the Organization, each object refer to `Post` model. - * @param membershipRequests - Collection of membership requests in the Organization, each object refer to `MembershipRequest` model. - * @param blockedUsers - Collection of Blocked User in the Organization, each object refer to `User` model. - * @param tags - Collection of tags. - * @param createdAt - Time stamp of data creation. - * @param updatedAt - Time stamp of data updation. + * Mongoose schema for an Organization. + * Defines the structure of the Organization document stored in MongoDB. + * @param apiUrl - API URL associated with the organization. + * @param image - URL of the organization's image. + * @param name - Name of the organization. + * @param description - Description of the organization. + * @param address - Address details of the organization. + * @param creatorId - Creator of the organization, referencing the User model. + * @param status - Status of the organization. + * @param members - Collection of members in the organization, each object referencing the User model. + * @param admins - Collection of admins in the organization, each object referencing the User model. + * @param advertisements - Collection of advertisements associated with the organization, each object referencing the Advertisement model. + * @param groupChats - Collection of group chats associated with the organization, each object referencing the Message model. + * @param posts - Collection of posts associated with the organization, each object referencing the Post model. + * @param pinnedPosts - Collection of pinned posts associated with the organization, each object referencing the Post model. + * @param membershipRequests - Collection of membership requests associated with the organization, each object referencing the MembershipRequest model. + * @param blockedUsers - Collection of blocked users associated with the organization, each object referencing the User model. + * @param customFields - Collection of custom fields associated with the organization, each object referencing the OrganizationCustomField model. + * @param funds - Collection of funds associated with the organization, each object referencing the Fund model. + * @param createdAt - Timestamp of when the organization was created. + * @param updatedAt - Timestamp of when the organization was last updated. + * @param userRegistrationRequired - Indicates if user registration is required for the organization. + * @param visibleInSearch - Indicates if the organization should be visible in search results. */ const organizationSchema = new Schema( { @@ -193,15 +199,25 @@ const organizationSchema = new Schema( ], }, { - timestamps: true, + timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields }, ); +// Add logging middleware for organizationSchema createLoggingMiddleware(organizationSchema, "Organization"); +/** + * Function to retrieve or create the Mongoose model for the Organization. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Organization. + */ const organizationModel = (): Model => model("Organization", organizationSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Organization. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const Organization = (models.Organization || organizationModel()) as ReturnType; diff --git a/src/models/OrganizationCustomField.ts b/src/models/OrganizationCustomField.ts index daeadb632f..282427b477 100644 --- a/src/models/OrganizationCustomField.ts +++ b/src/models/OrganizationCustomField.ts @@ -2,6 +2,9 @@ import type { Model } from "mongoose"; import mongoose from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Represents the structure of an organization custom field in the database. + */ export interface InterfaceOrganizationCustomField { _id: string; organizationId: string; @@ -9,6 +12,12 @@ export interface InterfaceOrganizationCustomField { name: string; } +/** + * Mongoose schema definition for OrganizationCustomField documents. + * @param organizationId - The ID of the organization to which this custom field belongs. + * @param type - The type of the custom field. + * @param name - The name of the custom field. + */ const organizationCustomFieldSchema = new mongoose.Schema({ organizationId: { type: mongoose.Schema.Types.String, @@ -26,6 +35,9 @@ const organizationCustomFieldSchema = new mongoose.Schema({ }, }); +/** + * Middleware to log database operations on the OrganizationCustomField collection. + */ createLoggingMiddleware( organizationCustomFieldSchema, "OrganizationCustomField", diff --git a/src/models/OrganizationTagUser.ts b/src/models/OrganizationTagUser.ts index ad885513f9..467a500df5 100644 --- a/src/models/OrganizationTagUser.ts +++ b/src/models/OrganizationTagUser.ts @@ -3,6 +3,9 @@ import { Schema, model, models } from "mongoose"; import type { InterfaceOrganization } from "./Organization"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Interface representing a document for an Organization Tag User in the database (MongoDB). + */ export interface InterfaceOrganizationTagUser { _id: Types.ObjectId; organizationId: PopulatedDoc; @@ -11,9 +14,14 @@ export interface InterfaceOrganizationTagUser { tagColor: string; } -// A User Tag is used for the categorization and the grouping of related users -// Each tag belongs to a particular organization, and is private to the same. -// Each tag can be nested to hold other sub-tags so as to create a heriecheal structure. +/** + * Mongoose schema for an Organization Tag User. + * Defines the structure of the Organization Tag User document stored in MongoDB. + * @param name - Name of the organization tag user. + * @param organizationId - Reference to the organization to which the tag belongs. + * @param parentTagId - Reference to the parent tag (if any) for hierarchical organization. + * @param tagColor - Color associated with the tag. + */ const organizationTagUserSchema = new Schema({ name: { type: String, @@ -37,19 +45,30 @@ const organizationTagUserSchema = new Schema({ }, }); +// Index to ensure unique combination of organizationId, parentOrganizationTagUserId, and name organizationTagUserSchema.index( { organizationId: 1, parentOrganizationTagUserId: 1, name: 1 }, { unique: true }, ); +// Add logging middleware for organizationTagUserSchema createLoggingMiddleware(organizationTagUserSchema, "OrganizationTagUser"); +/** + * Function to retrieve or create the Mongoose model for the Organization Tag User. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Organization Tag User. + */ const organizationTagUserModel = (): Model => model( "OrganizationTagUser", organizationTagUserSchema, ); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Organization Tag User. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const OrganizationTagUser = (models.OrganizationTagUser || organizationTagUserModel()) as ReturnType; diff --git a/src/models/Plugin.ts b/src/models/Plugin.ts index 74a544ac66..05228b1f85 100644 --- a/src/models/Plugin.ts +++ b/src/models/Plugin.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Plugin. + * Represents a MongoDB document for Plugin in the database. */ export interface InterfacePlugin { _id: Types.ObjectId; @@ -13,10 +14,11 @@ export interface InterfacePlugin { } /** - * @param pluginName- Name of the plugin preferred having underscores "_",(type: String) - * @param pluginCreatedBy- name of the plugin creator ex.John Doe,(type: String) - * @param pluginDesc- brief description of the plugin and it's features,(type: String) - * @param uninstalledOrgs - list of orgIDs which have disabled the feature on mobile app,(type: String[]) + * Mongoose schema definition for Plugin documents. + * @param pluginName - Name of the plugin preferred having underscores "_". + * @param pluginCreatedBy - Name of the plugin creator (e.g., "John Doe"). + * @param pluginDesc - Brief description of the plugin and its features. + * @param uninstalledOrgs - List of organization IDs which have disabled the feature on mobile app. */ const pluginSchema = new Schema({ @@ -41,6 +43,9 @@ const pluginSchema = new Schema({ ], }); +/** + * Middleware to log database operations on the Plugin collection. + */ createLoggingMiddleware(pluginSchema, "Plugin"); const pluginModel = (): Model => diff --git a/src/models/PluginField.ts b/src/models/PluginField.ts index 44e73f180e..88fcaa497d 100644 --- a/src/models/PluginField.ts +++ b/src/models/PluginField.ts @@ -1,8 +1,9 @@ import type { Types, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Plugin Field. + * Interface representing a document for a Plugin Field in the database (MongoDB). */ export interface InterfacePluginField { _id: Types.ObjectId; @@ -11,12 +12,14 @@ export interface InterfacePluginField { status: string; createdAt: Date; } + /** - * This describes the schema for a `PluginField` that corresponds to `InterfacePluginField` document. + * Mongoose schema for a Plugin Field. + * Defines the structure of the Plugin Field document stored in MongoDB. * @param key - Plugin key. - * @param value - Value. - * @param status - Status. - * @param createdAt - Time stamp of data creation. + * @param value - Value associated with the plugin key. + * @param status - Status of the plugin field. + * @param createdAt - Timestamp of data creation. */ const pluginFieldSchema = new Schema({ key: { @@ -39,11 +42,21 @@ const pluginFieldSchema = new Schema({ }, }); +// Add logging middleware for pluginFieldSchema createLoggingMiddleware(pluginFieldSchema, "PluginField"); +/** + * Function to retrieve or create the Mongoose model for the Plugin Field. + * This is necessary to avoid the OverwriteModelError during testing. + * @returns The Mongoose model for the Plugin Field. + */ const pluginFieldModel = (): Model => model("PluginField", pluginFieldSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Plugin Field. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const PluginField = (models.PluginField || pluginFieldModel()) as ReturnType; diff --git a/src/models/Post.ts b/src/models/Post.ts index 8bccf6d58d..f3bd86a624 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -4,8 +4,9 @@ import mongoosePaginate from "mongoose-paginate-v2"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; + /** - * This is an interface that represents a database(MongoDB) document for Post. + * Represents a MongoDB document for Post in the database. */ export interface InterfacePost { _id: Types.ObjectId; @@ -23,21 +24,20 @@ export interface InterfacePost { updatedAt: Date; videoUrl: string | undefined | null; } + /** - * This describes the schema for a `Post` that corresponds to `InterfacePost` document. - * @param commentCount - Post comments count. - * @param createdAt - Time stamp of data creation. - * @param creatorId - Post creator, refer to `User` model. - * @param imageUrl - Post attached image URL(if attached). - * @param likeCount - Post likes count. - * @param likedBy - Collection of user liked the post, each object refer to `User` model. - * @param pinned - Post pinned status - * @param organization - Organization data where the post is uploaded, refer to `Organization` model. - * @param status - Status. - * @param text - Post description. - * @param title - Post title. - * @param updatedAt - Time stamp of post updation - * @param videoUrl - Post attached video URL(if attached). + * Mongoose schema definition for Post documents. + * @param text - Content or description of the post. + * @param title - Title of the post. + * @param status - Status of the post. + * @param imageUrl - URL of the attached image, if any. + * @param videoUrl - URL of the attached video, if any. + * @param creatorId - Reference to the user who created the post. + * @param organization - Reference to the organization where the post is uploaded. + * @param likedBy - Array of users who liked the post. + * @param likeCount - Number of likes on the post. + * @param commentCount - Number of comments on the post. + * @param pinned - Indicates if the post is pinned. */ const postSchema = new Schema( { @@ -96,12 +96,16 @@ const postSchema = new Schema( }, ); +// Apply pagination plugin to the schema postSchema.plugin(mongoosePaginate); +// Ensure indexing for organization field for efficient querying postSchema.index({ organization: 1 }, { unique: false }); +// Middleware to log database operations on the Post collection createLoggingMiddleware(postSchema, "Post"); +// Define and export the model directly const postModel = (): PaginateModel => model>("Post", postSchema); diff --git a/src/models/RecurrenceRule.ts b/src/models/RecurrenceRule.ts index b00795cd93..28bb65f15f 100644 --- a/src/models/RecurrenceRule.ts +++ b/src/models/RecurrenceRule.ts @@ -4,7 +4,7 @@ import type { InterfaceEvent } from "./Event"; import type { InterfaceOrganization } from "./Organization"; /** - * This is an interface representing a document for a recurrence rule in the database(MongoDB). + * Enumeration for recurrence frequencies. */ export enum Frequency { @@ -14,6 +14,9 @@ export enum Frequency { DAILY = "DAILY", } +/** + * Enumeration for weekdays. + */ export enum WeekDays { SUNDAY = "SUNDAY", MONDAY = "MONDAY", @@ -24,6 +27,9 @@ export enum WeekDays { SATURDAY = "SATURDAY", } +/** + * Interface representing a document for a recurrence rule in the database (MongoDB). + */ export interface InterfaceRecurrenceRule { _id: Types.ObjectId; organizationId: PopulatedDoc; @@ -110,6 +116,10 @@ const recurrenceRuleSchema = new Schema( const recurrenceRuleModel = (): Model => model("RecurrenceRule", recurrenceRuleSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for the Recurrence Rule. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const RecurrenceRule = (models.RecurrenceRule || recurrenceRuleModel()) as ReturnType; diff --git a/src/models/SampleData.ts b/src/models/SampleData.ts index ddf8cb9c49..8f09cc609d 100644 --- a/src/models/SampleData.ts +++ b/src/models/SampleData.ts @@ -2,6 +2,9 @@ import type { Model, Document } from "mongoose"; import { Schema, model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Interface representing a document for sample data in the database (MongoDB). + */ export interface InterfaceSampleData extends Document { documentId: string; collectionName: @@ -13,6 +16,10 @@ export interface InterfaceSampleData extends Document { | "AppUserProfile"; } +/** + * Mongoose schema for sample data. + * Defines the structure of the sample data document stored in MongoDB. + */ const sampleDataSchema = new Schema({ documentId: { type: String, @@ -25,10 +32,21 @@ const sampleDataSchema = new Schema({ }, }); +// Create logging middleware for sampleDataSchema createLoggingMiddleware(sampleDataSchema, "SampleData"); +/** + * Function to retrieve or create the Mongoose model for Sample Data. + * This prevents the OverwriteModelError during testing. + * @returns The Mongoose model for Sample Data. + */ const sampleDataModel = (): Model => model("SampleData", sampleDataSchema); +/** + * The Mongoose model for Sample Data. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const SampleData = (models.SampleData || sampleDataModel()) as ReturnType; diff --git a/src/models/TagUser.ts b/src/models/TagUser.ts index e642594274..cd2ccb8d0c 100644 --- a/src/models/TagUser.ts +++ b/src/models/TagUser.ts @@ -4,14 +4,21 @@ import type { InterfaceOrganizationTagUser } from "./OrganizationTagUser"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +/** + * Represents a MongoDB document for TagUser in the database. + */ export interface InterfaceTagUser { _id: Types.ObjectId; userId: PopulatedDoc; tagId: PopulatedDoc; tagColor: PopulatedDoc; } - -// Relational schema used to keep track of assigned tags to users +/** + * Mongoose schema definition for TagUser documents. + * @param userId - Reference to the user associated with the tag. + * @param tagId - Reference to the tag associated with the user. + * @param tagColor - Color associated with the tag. + */ const tagUserSchema = new Schema({ userId: { type: Schema.Types.ObjectId, @@ -30,10 +37,13 @@ const tagUserSchema = new Schema({ }, }); +// Ensure uniqueness of tag assignments per user tagUserSchema.index({ userId: 1, tagId: 1 }, { unique: true }); +// Middleware to log database operations on the TagUser collection createLoggingMiddleware(tagUserSchema, "TagUser"); +// Define and export the model directly const tagUserModel = (): Model => model("TagUser", tagUserSchema); diff --git a/src/models/User.ts b/src/models/User.ts index 9d5f4341ba..a2c22cc7c4 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -9,7 +9,7 @@ import type { InterfaceMembershipRequest } from "./MembershipRequest"; import type { InterfaceOrganization } from "./Organization"; /** - * This is an interface that represents a database(MongoDB) document for User. + * Represents a MongoDB document for User in the database. */ export interface InterfaceUser { _id: Types.ObjectId; @@ -52,35 +52,29 @@ export interface InterfaceUser { updatedAt: Date; } -/** - * This describes the schema for a `User` that corresponds to `InterfaceUser` document. - * @param appUserProfileId - AppUserProfile id of the User - * @param address - User address - - * @param birthDate - User Date of birth - * @param createdAt - Time stamp of data creation. - - * @param educationGrade - User highest education degree - * @param email - User email id. - * @param employmentStatus - User employment status - - * @param firstName - User First Name. - * @param gender - User gender - * @param image - User Image URL. - * @param joinedOrganizations - Collection of the organization where user is the member, each object refer to `Organization` model. - * @param lastName - User Last Name. - * @param maritalStatus - User marital status - * @param membershipRequests - Collections of the membership request, each object refer to `MembershipRequest` model. - * @param organizationsBlockedBy - Collections of organizations where user is blocked, each object refer to `Organization` model. - * @param password - User hashed password. - * @param phone - User contact numbers, for mobile, home and work - - * @param registeredEvents - Collection of user registered Events, each object refer to `Event` model. - * @param status - Status - * - - * @param updatedAt - Timestamp of data updation +/** + * Mongoose schema definition for User documents. + * @param appUserProfileId - Reference to the user's app profile. + * @param address - User's address details. + * @param birthDate - User's date of birth. + * @param createdAt - Timestamp of when the user was created. + * @param educationGrade - User's highest education grade. + * @param email - User's email address (validated as an email). + * @param employmentStatus - User's employment status. + * @param firstName - User's first name. + * @param gender - User's gender. + * @param image - URL to the user's image. + * @param joinedOrganizations - Organizations the user has joined. + * @param lastName - User's last name. + * @param maritalStatus - User's marital status. + * @param membershipRequests - Membership requests made by the user. + * @param organizationsBlockedBy - Organizations that have blocked the user. + * @param password - User's hashed password. + * @param phone - User's contact numbers (home, mobile, work). + * @param registeredEvents - Events the user has registered for. + * @param status - User's status (ACTIVE, BLOCKED, DELETED). + * @param updatedAt - Timestamp of when the user was last updated. */ const userSchema = new Schema( { @@ -117,7 +111,6 @@ const userSchema = new Schema( birthDate: { type: Date, }, - educationGrade: { type: String, enum: [ @@ -232,6 +225,7 @@ const userSchema = new Schema( userSchema.plugin(mongoosePaginate); +// Create and export the User model const userModel = (): PaginateModel => model>("User", userSchema); diff --git a/src/models/UserCustomData.ts b/src/models/UserCustomData.ts index 1c90d57e69..f4198255fd 100644 --- a/src/models/UserCustomData.ts +++ b/src/models/UserCustomData.ts @@ -3,7 +3,7 @@ import mongoose, { model, models } from "mongoose"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** - * This is an interface representing a document for custom field in the database(MongoDB). + * Interface representing a document for custom field in the database (MongoDB). */ export interface InterfaceUserCustomData { _id: string; @@ -38,11 +38,21 @@ const userCustomDataSchema = new mongoose.Schema({ }, }); +// Create logging middleware for userCustomDataSchema createLoggingMiddleware(userCustomDataSchema, "UserCustomData"); +/** + * Function to retrieve or create the Mongoose model for User Custom Data. + * This prevents the OverwriteModelError during testing. + * @returns The Mongoose model for User Custom Data. + */ const userCustomData = (): Model => model("UserCustomData", userCustomDataSchema); -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +/** + * The Mongoose model for User Custom Data. + * If the model already exists (e.g., during testing), it uses the existing model. + * Otherwise, it creates a new model. + */ export const UserCustomData = (models.UserCustomData || userCustomData()) as ReturnType; diff --git a/src/models/Venue.ts b/src/models/Venue.ts index 6e81a76417..1250811a7e 100644 --- a/src/models/Venue.ts +++ b/src/models/Venue.ts @@ -12,14 +12,13 @@ export interface InterfaceVenue { } /** - * This describes the schema for a Venue that corresponds to InterfaceVenue document. + * Mongoose schema definition for Venue documents. * @param name - Name of the venue. * @param description - Description of the venue. * @param capacity - Maximum capacity of the venue. - * @param imageUrl - Image URL(if attached) of the venue. - * @param organization - Organization in which the venue belongs. + * @param imageUrl - Image URL (if attached) of the venue. + * @param organization - Reference to the organization that owns the venue. */ - const venueSchema = new Schema({ name: { type: String, @@ -43,6 +42,7 @@ const venueSchema = new Schema({ }, }); +// Create and export the Venue model const venueModel = (): Model => model("Venue", venueSchema); diff --git a/src/models/userFamily.ts b/src/models/userFamily.ts index a9d8577ea4..b1a17598ed 100644 --- a/src/models/userFamily.ts +++ b/src/models/userFamily.ts @@ -1,10 +1,10 @@ import type { PopulatedDoc, Types, Document, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import type { InterfaceUser } from "./User"; + /** - * This is an interface that represents a database(MongoDB) document for Family. + * Interface representing a MongoDB document for User Family. */ - export interface InterfaceUserFamily { _id: Types.ObjectId; title: string; @@ -14,13 +14,11 @@ export interface InterfaceUserFamily { } /** - * @param title - Name of the user Family (type: String) - * Description: Name of the user Family. - */ - -/** - * @param users - Members associated with the user Family (type: String) - * Description: Members associated with the user Family. + * Mongoose schema definition for User Family documents. + * @param title - Name of the user Family. + * @param users - Members associated with the user Family. + * @param admins - Admins of the user Family. + * @param creator - Creator of the user Family. */ const userFamilySchema = new Schema({ title: { @@ -48,6 +46,7 @@ const userFamilySchema = new Schema({ }, }); +// Create and export the UserFamily model const userFamilyModel = (): Model => model("UserFamily", userFamilySchema); diff --git a/src/resolvers/ActionItem/actionItemCategory.ts b/src/resolvers/ActionItem/actionItemCategory.ts index 146d836017..47683a0203 100644 --- a/src/resolvers/ActionItem/actionItemCategory.ts +++ b/src/resolvers/ActionItem/actionItemCategory.ts @@ -1,6 +1,11 @@ import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; import { ActionItemCategory } from "../../models"; +/** + * Resolver function to fetch the category of an action item. + * @param parent - The parent object containing the action item data. + * @returns The category of the action item found in the database. + */ export const actionItemCategory: ActionItemResolvers["actionItemCategory"] = async (parent) => { return ActionItemCategory.findOne({ diff --git a/src/resolvers/ActionItem/assignee.ts b/src/resolvers/ActionItem/assignee.ts index c3f3d13090..22c4c9a560 100644 --- a/src/resolvers/ActionItem/assignee.ts +++ b/src/resolvers/ActionItem/assignee.ts @@ -1,6 +1,20 @@ import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `assignee` field of an `ActionItem`. + * + * This function fetches the user who is assigned to a specific action item. + * + * @param parent - The parent object representing the action item. It contains information about the action item, including the ID of the user assigned to it. + * @returns A promise that resolves to the user document found in the database. This document represents the user assigned to the action item. + * + * @example + * If the action item with an ID of `123` is assigned to a user with an ID of `456`, this resolver will find the user with the ID `456` in the database and return their information. + * + * @see User - The User model used to interact with the users collection in the database. + * @see ActionItemResolvers - The type definition for the resolvers of the ActionItem fields. + */ export const assignee: ActionItemResolvers["assignee"] = async (parent) => { return User.findOne({ _id: parent.assigneeId, diff --git a/src/resolvers/ActionItem/assigner.ts b/src/resolvers/ActionItem/assigner.ts index 1668d3b4a3..7b763a06d4 100644 --- a/src/resolvers/ActionItem/assigner.ts +++ b/src/resolvers/ActionItem/assigner.ts @@ -1,6 +1,17 @@ import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `assigner` field of an `ActionItem`. + * + * This function fetches the user who is the assigner of a given action item. + * It uses the `assignerId` field from the parent `ActionItem` object to find the corresponding user in the database. + * The user details are then returned in a plain JavaScript object format. + * + * @param parent - The parent `ActionItem` object. This contains the `assignerId` field, which is used to find the user. + * @returns A promise that resolves to the user object found in the database, or `null` if no user is found. + * + */ export const assigner: ActionItemResolvers["assigner"] = async (parent) => { return User.findOne({ _id: parent.assignerId, diff --git a/src/resolvers/ActionItem/creator.ts b/src/resolvers/ActionItem/creator.ts index 70dbf78957..f286ba0d93 100644 --- a/src/resolvers/ActionItem/creator.ts +++ b/src/resolvers/ActionItem/creator.ts @@ -1,6 +1,26 @@ import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `creator` field of an `ActionItem`. + * + * This function fetches the user who is the creator of a given action item. + * It uses the `creatorId` field from the parent `ActionItem` object to find the corresponding user in the database. + * The user details are then returned in a plain JavaScript object format. + * + * @param parent - The parent `ActionItem` object. This contains the `creatorId` field, which is used to find the user. + * @returns A promise that resolves to the user object found in the database, or `null` if no user is found. + * + * @example + * ```typescript + * const actionItem = { + * creatorId: "60d0fe4f5311236168a109cb" + * }; + * const user = await creator(actionItem); + * console.log(user); + * // Output might be: { _id: "60d0fe4f5311236168a109cb", name: "Jane Doe", email: "jane.doe@example.com" } + * ``` + */ export const creator: ActionItemResolvers["creator"] = async (parent) => { return User.findOne({ _id: parent.creatorId, diff --git a/src/resolvers/ActionItem/event.ts b/src/resolvers/ActionItem/event.ts index e79144e5a9..b4584d964f 100644 --- a/src/resolvers/ActionItem/event.ts +++ b/src/resolvers/ActionItem/event.ts @@ -1,6 +1,30 @@ import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `event` field of an `ActionItem`. + * + * This function retrieves the event associated with a specific action item. + * + * @param parent - The parent object representing the action item. It contains information about the action item, including the ID of the associated event. + * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the action item. + * + * @example + * Here's how you might use this resolver in your GraphQL schema: + * + * ```graphql + * type ActionItem { + * event: Event + * # other fields... + * } + * ``` + * + * @example + * If the action item with an ID of `123` is associated with an event with an ID of `789`, this resolver will find the event with the ID `789` in the database and return its information. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see ActionItemResolvers - The type definition for the resolvers of the ActionItem fields. + */ export const event: ActionItemResolvers["event"] = async (parent) => { return Event.findOne({ _id: parent.eventId, diff --git a/src/resolvers/ActionItemCategory/creator.ts b/src/resolvers/ActionItemCategory/creator.ts index 8ccffc6a6a..e607824a11 100644 --- a/src/resolvers/ActionItemCategory/creator.ts +++ b/src/resolvers/ActionItemCategory/creator.ts @@ -1,6 +1,17 @@ import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `creator` field of an `ActionItemCategory`. + * + * This function retrieves the user who created a specific action item category. + * + * @param parent - The parent object representing the action item category. It contains information about the action item category, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the action item category. + * + * @see User - The User model used to interact with the users collection in the database. + * @see ActionItemCategoryResolvers - The type definition for the resolvers of the ActionItemCategory fields. + */ export const creator: ActionItemCategoryResolvers["creator"] = async ( parent, ) => { diff --git a/src/resolvers/ActionItemCategory/organization.ts b/src/resolvers/ActionItemCategory/organization.ts index 3981d6cb65..5c800a5ada 100644 --- a/src/resolvers/ActionItemCategory/organization.ts +++ b/src/resolvers/ActionItemCategory/organization.ts @@ -1,6 +1,26 @@ import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; +/** + * Resolver function for the `organization` field of an `ActionItemCategory`. + * + * This function fetches the organization associated with a given action item category. + * It uses the `organizationId` field from the parent `ActionItemCategory` object to find the corresponding organization in the database. + * The organization details are then returned in a plain JavaScript object format. + * + * @param parent - The parent `ActionItemCategory` object. This contains the `organizationId` field, which is used to find the organization. + * @returns A promise that resolves to the organization object found in the database, or `null` if no organization is found. + * + * @example + * ```typescript + * const actionItemCategory = { + * organizationId: "60d0fe4f5311236168a109cc" + * }; + * const organization = await organization(actionItemCategory); + * console.log(organization); + * // Output might be: { _id: "60d0fe4f5311236168a109cc", name: "Tech Corp", address: "123 Tech Lane" } + * ``` + */ export const organization: ActionItemCategoryResolvers["organization"] = async ( parent, ) => { diff --git a/src/resolvers/Advertisement/organization.ts b/src/resolvers/Advertisement/organization.ts index fe165a8f4e..51e49d0f68 100644 --- a/src/resolvers/Advertisement/organization.ts +++ b/src/resolvers/Advertisement/organization.ts @@ -1,6 +1,17 @@ import type { AdvertisementResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; +/** + * Resolver function for the `organization` field of an `Advertisement`. + * + * This function fetches the organization associated with a given advertisement. + * It uses the `organizationId` field from the parent `Advertisement` object to find the corresponding organization in the database. + * The organization details are then returned in a plain JavaScript object format. + * + * @param parent - The parent `Advertisement` object. This contains the `organizationId` field, which is used to find the organization. + * @returns A promise that resolves to the organization object found in the database, or `null` if no organization is found. + * + */ export const organization: AdvertisementResolvers["organization"] = async ( parent, ) => { diff --git a/src/resolvers/AgendaCategory/createdBy.ts b/src/resolvers/AgendaCategory/createdBy.ts index 98924a6ddd..15858d6cf9 100644 --- a/src/resolvers/AgendaCategory/createdBy.ts +++ b/src/resolvers/AgendaCategory/createdBy.ts @@ -1,5 +1,18 @@ import type { AgendaCategoryResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; + +/** + * Resolver function for the `createdBy` field of an `AgendaCategory`. + * + * This function retrieves the user who created a specific agenda category. + * + * @param parent - The parent object representing the agenda category. It contains information about the agenda category, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the agenda category. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AgendaCategoryResolvers - The type definition for the resolvers of the AgendaCategory fields. + * + */ //@ts-expect-error - type error export const createdBy: AgendaCategoryResolvers["createdBy"] = async ( parent, diff --git a/src/resolvers/AgendaCategory/organization.ts b/src/resolvers/AgendaCategory/organization.ts index de292433c3..8ec02fd33c 100644 --- a/src/resolvers/AgendaCategory/organization.ts +++ b/src/resolvers/AgendaCategory/organization.ts @@ -1,5 +1,17 @@ import type { AgendaCategoryResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; + +/** + * Resolver function for the `organization` field of an `AgendaCategory`. + * + * This function fetches the organization associated with a given agenda category. + * It uses the `organizationId` field from the parent `AgendaCategory` object to find the corresponding organization in the database. + * The organization details are then returned in a plain JavaScript object format. + * + * @param parent - The parent `AgendaCategory` object. This contains the `organizationId` field, which is used to find the organization. + * @returns A promise that resolves to the organization object found in the database, or `null` if no organization is found. + */ + //@ts-expect-error - type error export const organization: AgendaCategoryResolvers["organization"] = async ( parent, diff --git a/src/resolvers/AgendaCategory/updatedBy.ts b/src/resolvers/AgendaCategory/updatedBy.ts index 12bf122f8b..fd949c595f 100644 --- a/src/resolvers/AgendaCategory/updatedBy.ts +++ b/src/resolvers/AgendaCategory/updatedBy.ts @@ -1,6 +1,21 @@ import type { AgendaCategoryResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `updatedBy` field of an `AgendaCategory`. + * + * This function retrieves the user who last updated a specific agenda category. + * + * @param parent - The parent object representing the agenda category. It contains information about the agenda category, including the ID of the user who last updated it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who last updated the agenda category. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AgendaCategoryResolvers - The type definition for the resolvers of the AgendaCategory fields. + * + * ```typescript + * return User.findOne({ _id: parent.updatedBy }).lean(); + * ``` + */ export const updatedBy: AgendaCategoryResolvers["updatedBy"] = async ( parent, ) => { diff --git a/src/resolvers/AgendaItem/Users.ts b/src/resolvers/AgendaItem/Users.ts index 93810885c1..df57368d8c 100644 --- a/src/resolvers/AgendaItem/Users.ts +++ b/src/resolvers/AgendaItem/Users.ts @@ -1,6 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `users` field of an `AgendaItem`. + * + * This function retrieves the users associated with a specific agenda item. + * + * @param parent - The parent object representing the agenda item. It contains information about the agenda item, including the IDs of the users associated with it. + * @returns A promise that resolves to the user documents found in the database. These documents represent the users associated with the agenda item. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AgendaItemResolvers - The type definition for the resolvers of the AgendaItem fields. + * + */ export const users: AgendaItemResolvers["users"] = async (parent) => { const userIds = parent.users; // Assuming parent.users is an array of user ids const users = await User.find({ _id: { $in: userIds } }); // Assuming User.find() returns a promise diff --git a/src/resolvers/AgendaItem/categories.ts b/src/resolvers/AgendaItem/categories.ts index 8e7f155c0a..464b78bda0 100644 --- a/src/resolvers/AgendaItem/categories.ts +++ b/src/resolvers/AgendaItem/categories.ts @@ -1,6 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { AgendaCategoryModel } from "../../models"; -//@ts-expect-error - type error + +/** + * Resolver function for the `categories` field of an `AgendaItem`. + * + * This function retrieves the categories associated with a specific agenda item. + * + * @param parent - The parent object representing the agenda item. It contains a list of category IDs associated with it. + * @returns A promise that resolves to an array of category documents found in the database. These documents represent the categories associated with the agenda item. + * + * @see AgendaCategoryModel - The model used to interact with the categories collection in the database. + * @see AgendaItemResolvers - The type definition for the resolvers of the AgendaItem fields. + * + */ export const categories: AgendaItemResolvers["categories"] = async (parent) => { const relatedCategoryIds = parent.categories; diff --git a/src/resolvers/AgendaItem/createdBy.ts b/src/resolvers/AgendaItem/createdBy.ts index fd1f09e670..3323c72324 100644 --- a/src/resolvers/AgendaItem/createdBy.ts +++ b/src/resolvers/AgendaItem/createdBy.ts @@ -1,7 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; -//@ts-expect-error - type error +/** + * Resolver function for the `createdBy` field of an `AgendaItem`. + * + * This function retrieves the user who created a specific agenda item. + * It uses the `createdBy` field from the parent `AgendaItem` object to find the corresponding user in the database. + * The user details are then returned as a plain JavaScript object. + * + * @param parent - The parent `AgendaItem` object. This contains the `createdBy` field, which is used to query the user. + * @returns A promise that resolves to the user object found in the database, or `null` if no user is found. + * + */ +//@ts-expect-error - type error export const createdBy: AgendaItemResolvers["createdBy"] = async (parent) => { return User.findOne(parent.createdBy).lean(); }; diff --git a/src/resolvers/AgendaItem/organization.ts b/src/resolvers/AgendaItem/organization.ts index 0baa962844..3e0632d757 100644 --- a/src/resolvers/AgendaItem/organization.ts +++ b/src/resolvers/AgendaItem/organization.ts @@ -1,5 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; + +/** + * Resolver function for the `organization` field of an `AgendaItem`. + * + * This function retrieves the organization associated with a specific agenda item. + * + * @param parent - The parent object representing the agenda item. It contains information about the agenda item, including the ID of the organization it is associated with. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the agenda item. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see AgendaItemResolvers - The type definition for the resolvers of the AgendaItem fields. + * + */ //@ts-expect-error - type error export const organization: AgendaItemResolvers["organization"] = async ( diff --git a/src/resolvers/AgendaItem/relatedEvent.ts b/src/resolvers/AgendaItem/relatedEvent.ts index 1a0a41794a..1ef36bc56a 100644 --- a/src/resolvers/AgendaItem/relatedEvent.ts +++ b/src/resolvers/AgendaItem/relatedEvent.ts @@ -1,6 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `relatedEvent` field of an `AgendaItem`. + * + * This function retrieves the event related to a specific agenda item. + * + * @param parent - The parent object representing the agenda item. It contains information about the agenda item, including the ID of the related event. + * @returns A promise that resolves to the event document found in the database. This document represents the event related to the agenda item. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see AgendaItemResolvers - The type definition for the resolvers of the AgendaItem fields. + * + */ export const relatedEvent: AgendaItemResolvers["relatedEvent"] = async ( parent, ) => { diff --git a/src/resolvers/AgendaItem/updatedBy.ts b/src/resolvers/AgendaItem/updatedBy.ts index 84a0e4ea78..c3706f4274 100644 --- a/src/resolvers/AgendaItem/updatedBy.ts +++ b/src/resolvers/AgendaItem/updatedBy.ts @@ -1,5 +1,18 @@ import type { AgendaItemResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; + +/** + * Resolver function for the `updatedBy` field of an `AgendaItem`. + * + * This function retrieves the user who last updated a specific agenda item. + * + * @param parent - The parent object representing the agenda item. It contains information about the agenda item, including the ID of the user who last updated it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who last updated the agenda item. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AgendaItemResolvers - The type definition for the resolvers of the AgendaItem fields. + * + */ //@ts-expect-error - type error export const updatedBy: AgendaItemResolvers["updatedBy"] = async (parent) => { diff --git a/src/resolvers/AgendaSection/createdBy.ts b/src/resolvers/AgendaSection/createdBy.ts index f5106c8820..40e7e3c4a0 100644 --- a/src/resolvers/AgendaSection/createdBy.ts +++ b/src/resolvers/AgendaSection/createdBy.ts @@ -1,6 +1,18 @@ import type { AgendaSectionResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `createdBy` field of an `AgendaSection`. + * + * This function retrieves the user who created a specific agenda section. + * + * @param parent - The parent object representing the agenda section. It contains information about the agenda section, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the agenda section. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AgendaSectionResolvers - The type definition for the resolvers of the AgendaSection fields. + * + */ export const createdBy: AgendaSectionResolvers["createdBy"] = async ( parent, ) => { diff --git a/src/resolvers/AgendaSection/index.ts b/src/resolvers/AgendaSection/index.ts index 9fb981b9c0..3b242a9d94 100644 --- a/src/resolvers/AgendaSection/index.ts +++ b/src/resolvers/AgendaSection/index.ts @@ -3,6 +3,17 @@ import { createdBy } from "./createdBy"; import { items } from "./items"; import { relatedEvent } from "./relatedEvent"; +/** + * Resolver function for the `AgendaSection` type. + * + * This resolver is used to resolve the fields of an `AgendaSection` type. + * + * @see relatedEvent - The resolver function for the `relatedEvent` field of an `AgendaSection`. + * @see items - The resolver function for the `items` field of an `AgendaSection`. + * @see createdBy - The resolver function for the `createdBy` field of an `AgendaSection`. + * @see AgendaSectionResolvers - The type definition for the resolvers of the AgendaSection fields. + * + */ export const AgendaSection: AgendaSectionResolvers = { relatedEvent, items, diff --git a/src/resolvers/AgendaSection/items.ts b/src/resolvers/AgendaSection/items.ts index 73f58e10aa..4f8fe85dcf 100644 --- a/src/resolvers/AgendaSection/items.ts +++ b/src/resolvers/AgendaSection/items.ts @@ -1,6 +1,19 @@ import type { AgendaSectionResolvers } from "../../types/generatedGraphQLTypes"; import { AgendaItemModel } from "../../models"; +/** + * Resolver function for the `items` field of an `AgendaSection`. + * + * This function retrieves the agenda items associated with a specific agenda section. + * + * @param parent - The parent object representing the agenda section. It contains information about the agenda section, including the IDs of the agenda items associated with it. + * @returns A promise that resolves to the agenda item documents found in the database. These documents represent the agenda items associated with the agenda section. + * + * @see AgendaItemModel - The AgendaItem model used to interact with the agenda items collection in the database. + * @see AgendaSectionResolvers - The type definition for the resolvers of the AgendaSection fields. + * + */ + export const items: AgendaSectionResolvers["items"] = async (parent) => { const relatedAgendaItemIds = parent.items; diff --git a/src/resolvers/AgendaSection/relatedEvent.ts b/src/resolvers/AgendaSection/relatedEvent.ts index 7c60755d67..0fd7adc161 100644 --- a/src/resolvers/AgendaSection/relatedEvent.ts +++ b/src/resolvers/AgendaSection/relatedEvent.ts @@ -1,6 +1,19 @@ import type { AgendaSectionResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `relatedEvent` field of an `AgendaSection`. + * + * This function retrieves the event related to a specific agenda section. + * + * @param parent - The parent object representing the agenda section. It contains information about the agenda section, including the ID of the related event. + * @returns A promise that resolves to the event document found in the database. This document represents the event related to the agenda section. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see AgendaSectionResolvers - The type definition for the resolvers of the AgendaSection fields. + * + */ + export const relatedEvent: AgendaSectionResolvers["relatedEvent"] = async ( parent, ) => { diff --git a/src/resolvers/CheckIn/event.ts b/src/resolvers/CheckIn/event.ts index 39bb0ba264..b6f8af20db 100644 --- a/src/resolvers/CheckIn/event.ts +++ b/src/resolvers/CheckIn/event.ts @@ -1,6 +1,18 @@ import type { CheckInResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; +/** + * Resolver function for the `event` field of a `CheckIn`. + * + * This function retrieves the event associated with a specific check-in. + * + * @param parent - The parent object representing the check-in. It contains information about the check-in, including the ID of the event attendee it is associated with. + * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the check-in. + * + * @see EventAttendee - The EventAttendee model used to interact with the event attendees collection in the database. + * @see CheckInResolvers - The type definition for the resolvers of the CheckIn fields. + * + */ export const event: CheckInResolvers["event"] = async (parent) => { const attendeeObject = await EventAttendee.findOne({ _id: parent.eventAttendeeId, diff --git a/src/resolvers/CheckIn/user.ts b/src/resolvers/CheckIn/user.ts index cd2c778835..4db1f9f2b1 100644 --- a/src/resolvers/CheckIn/user.ts +++ b/src/resolvers/CheckIn/user.ts @@ -1,6 +1,18 @@ import type { CheckInResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; +/** + * Resolver function for the `user` field of a `CheckIn`. + * + * This function retrieves the user who checked in to an event. + * + * @param parent - The parent object representing the check-in. It contains information about the check-in, including the ID of the event attendee who checked in. + * @returns A promise that resolves to the user document found in the database. This document represents the user who checked in to the event. + * + * @see EventAttendee - The EventAttendee model used to interact with the event attendees collection in the database. + * @see CheckInResolvers - The type definition for the resolvers of the CheckIn fields. + * + */ export const user: CheckInResolvers["user"] = async (parent) => { const attendeeObject = await EventAttendee.findOne({ _id: parent.eventAttendeeId, diff --git a/src/resolvers/Comment/creator.ts b/src/resolvers/Comment/creator.ts index 7c3973330c..56248ae22d 100644 --- a/src/resolvers/Comment/creator.ts +++ b/src/resolvers/Comment/creator.ts @@ -1,6 +1,18 @@ import type { CommentResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `creator` field of a `Comment`. + * + * This function retrieves the user who created a specific comment. + * + * @param parent - The parent object representing the comment. It contains information about the comment, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the comment. + * + * @see User - The User model used to interact with the users collection in the database. + * @see CommentResolvers - The type definition for the resolvers of the Comment fields. + * + */ export const creator: CommentResolvers["creator"] = async (parent) => { return await User.findOne({ _id: parent.creatorId, diff --git a/src/resolvers/DirectChat/index.ts b/src/resolvers/DirectChat/index.ts index a9147d84a2..8a0f6a4059 100644 --- a/src/resolvers/DirectChat/index.ts +++ b/src/resolvers/DirectChat/index.ts @@ -4,6 +4,18 @@ import { messages } from "./messages"; import { organization } from "./organization"; import { users } from "./users"; +/** + * Resolver function for the `DirectChat` type. + * + * This resolver is used to resolve the fields of a `DirectChat` type. + * + * @see users - The resolver function for the `users` field of a `DirectChat`. + * @see organization - The resolver function for the `organization` field of a `DirectChat`. + * @see messages - The resolver function for the `messages` field of a `DirectChat`. + * @see creator - The resolver function for the `creator` field of a `DirectChat`. + * @see DirectChatResolvers - The type definition for the resolvers of the DirectChat fields. + * + */ export const DirectChat: DirectChatResolvers = { creator, messages, diff --git a/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts b/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts index c42bcaa9c5..eb6a0d4d32 100644 --- a/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts +++ b/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts @@ -2,10 +2,18 @@ import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTyp import { DirectChat } from "../../models"; import { CHAT_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver method will retrieve and return from the database the Direct chat to which the specified message belongs. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains the Direct chat data. + * Resolver function for the `directChatMessageBelongsTo` field of a `DirectChatMessage`. + * + * This function retrieves the direct chat to which a specific direct chat message belongs. + * + * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the direct chat to which it belongs. + * @returns A promise that resolves to the direct chat document found in the database. This document represents the direct chat to which the direct chat message belongs. + * + * @see DirectChat - The DirectChat model used to interact with the direct chats collection in the database. + * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. + * */ export const directChatMessageBelongsTo: DirectChatMessageResolvers["directChatMessageBelongsTo"] = async (parent) => { diff --git a/src/resolvers/DirectChatMessage/receiver.ts b/src/resolvers/DirectChatMessage/receiver.ts index 93dd67b24f..1702f33b66 100644 --- a/src/resolvers/DirectChatMessage/receiver.ts +++ b/src/resolvers/DirectChatMessage/receiver.ts @@ -2,10 +2,18 @@ import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTyp import { User } from "../../models"; import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver function will fetch and return the receiver(user) of the Direct chat from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains User's data. + * Resolver function for the `receiver` field of a `DirectChatMessage`. + * + * This function retrieves the user who received a specific direct chat message. + * + * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the user who received it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who received the direct chat message. + * + * @see User - The User model used to interact with the users collection in the database. + * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. + * */ export const receiver: DirectChatMessageResolvers["receiver"] = async ( parent, diff --git a/src/resolvers/DirectChatMessage/sender.ts b/src/resolvers/DirectChatMessage/sender.ts index 655b677b58..9a20bb4490 100644 --- a/src/resolvers/DirectChatMessage/sender.ts +++ b/src/resolvers/DirectChatMessage/sender.ts @@ -2,10 +2,18 @@ import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTyp import { User } from "../../models"; import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver function will fetch and return the sender(user) of the Direct chat from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains User's data. + * Resolver function for the `sender` field of a `DirectChatMessage`. + * + * This function retrieves the user who sent a specific direct chat message. + * + * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the user who sent it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who sent the direct chat message. + * + * @see User - The User model used to interact with the users collection in the database. + * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. + * */ export const sender: DirectChatMessageResolvers["sender"] = async (parent) => { const result = await User.findOne({ diff --git a/src/resolvers/Event/actionItems.ts b/src/resolvers/Event/actionItems.ts index 0fcc7b29fb..b40c8a703c 100644 --- a/src/resolvers/Event/actionItems.ts +++ b/src/resolvers/Event/actionItems.ts @@ -1,9 +1,17 @@ import { ActionItem } from "../../models"; import type { EventResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the action items related to the event from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all action items related to the event. + * Resolver function for the `actionItems` field of an `Event`. + * + * This function retrieves the action items associated with a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the action items associated with it. + * @returns A promise that resolves to an array of action item documents found in the database. These documents represent the action items associated with the event. + * + * @see ActionItem - The ActionItem model used to interact with the action items collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * */ export const actionItems: EventResolvers["actionItems"] = async (parent) => { return await ActionItem.find({ diff --git a/src/resolvers/Event/attendees.ts b/src/resolvers/Event/attendees.ts index 547bf38b61..c6b24c1f4d 100644 --- a/src/resolvers/Event/attendees.ts +++ b/src/resolvers/Event/attendees.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; +/** + * Resolver function for the `attendees` field of an `Event`. + * + * This function retrieves the attendees of an event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the event. + * @returns A promise that resolves to the user documents found in the database. These documents represent the attendees of the event. + * + * @see EventAttendee - The EventAttendee model used to interact with the event attendees collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const attendees: EventResolvers["attendees"] = async (parent) => { const eventAttendeeObjects = await EventAttendee.find({ eventId: parent._id, diff --git a/src/resolvers/Event/attendeesCheckInStatus.ts b/src/resolvers/Event/attendeesCheckInStatus.ts index 25ebb3f1d7..7935f4b98e 100644 --- a/src/resolvers/Event/attendeesCheckInStatus.ts +++ b/src/resolvers/Event/attendeesCheckInStatus.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; +/** + * Resolver function for the `attendeesCheckInStatus` field of an `Event`. + * + * This function retrieves the attendees of an event and their check-in status. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID. + * @returns A promise that resolves to an array of objects. Each object contains information about an attendee of the event, including the user document and the check-in document. + * + * @see EventAttendee - The EventAttendee model used to interact with the event attendees collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const attendeesCheckInStatus: EventResolvers["attendeesCheckInStatus"] = async (parent) => { const eventAttendeeObjects = await EventAttendee.find({ diff --git a/src/resolvers/Event/averageFeedbackScore.ts b/src/resolvers/Event/averageFeedbackScore.ts index 087eca38e9..3f13f2ee0b 100644 --- a/src/resolvers/Event/averageFeedbackScore.ts +++ b/src/resolvers/Event/averageFeedbackScore.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { Feedback } from "../../models"; +/** + * Resolver function for the `averageFeedbackScore` field of an `Event`. + * + * This function calculates the average feedback score for a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the feedback associated with it. + * @returns A promise that resolves to the average feedback score for the event. + * + * @see Feedback - The Feedback model used to interact with the feedback collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const averageFeedbackScore: EventResolvers["averageFeedbackScore"] = async (parent) => { const feedbacks = await Feedback.find({ diff --git a/src/resolvers/Event/baseRecurringEvent.ts b/src/resolvers/Event/baseRecurringEvent.ts index 35ef6f4688..d42ecb4bb2 100644 --- a/src/resolvers/Event/baseRecurringEvent.ts +++ b/src/resolvers/Event/baseRecurringEvent.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `baseRecurringEvent` field of an `Event`. + * + * This function retrieves the base recurring event of a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the base recurring event. + * @returns A promise that resolves to the event document found in the database. This document represents the base recurring event of the event. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const baseRecurringEvent: EventResolvers["baseRecurringEvent"] = async ( parent, ) => { diff --git a/src/resolvers/Event/creator.ts b/src/resolvers/Event/creator.ts index 2b53c49fe5..233eeb94b9 100644 --- a/src/resolvers/Event/creator.ts +++ b/src/resolvers/Event/creator.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `creator` field of an `Event`. + * + * This function retrieves the user who created a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event. + * + * @see User - The User model used to interact with the users collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const creator: EventResolvers["creator"] = async (parent) => { return User.findOne({ _id: parent.creatorId, diff --git a/src/resolvers/Event/feedback.ts b/src/resolvers/Event/feedback.ts index f362b6b2af..b712116c00 100644 --- a/src/resolvers/Event/feedback.ts +++ b/src/resolvers/Event/feedback.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { Feedback } from "../../models"; +/** + * Resolver function for the `feedback` field of an `Event`. + * + * This function retrieves the feedback associated with a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the feedback associated with it. + * @returns A promise that resolves to an array of feedback documents found in the database. These documents represent the feedback associated with the event. + * + * @see Feedback - The Feedback model used to interact with the feedback collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const feedback: EventResolvers["feedback"] = async (parent) => { return Feedback.find({ eventId: parent._id, diff --git a/src/resolvers/Event/organization.ts b/src/resolvers/Event/organization.ts index 1bc86dfc8e..db115dcfce 100644 --- a/src/resolvers/Event/organization.ts +++ b/src/resolvers/Event/organization.ts @@ -1,6 +1,18 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; +/** + * Resolver function for the `organization` field of an `Event`. + * + * This function retrieves the organization associated with a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the organization it is associated with. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the event. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * + */ export const organization: EventResolvers["organization"] = async (parent) => { return Organization.findOne({ _id: parent.organization, diff --git a/src/resolvers/Event/recurrenceRule.ts b/src/resolvers/Event/recurrenceRule.ts index 5e26b84b3c..74fd20050c 100644 --- a/src/resolvers/Event/recurrenceRule.ts +++ b/src/resolvers/Event/recurrenceRule.ts @@ -2,9 +2,16 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; import { RecurrenceRule } from "../../models/RecurrenceRule"; /** - * This resolver function will fetch and return the recurrenceRule that the event is following. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the RecurrenceRule of the event. + * Resolver function for the `recurrenceRule` field of an `Event`. + * + * This function retrieves the recurrence rule associated with a specific event. + * + * @param parent - The parent object representing the event. It contains information about the event, including the ID of the recurrence rule associated with it. + * @returns A promise that resolves to the recurrence rule document found in the database. This document represents the recurrence rule associated with the event. + * + * @see RecurrenceRule - The RecurrenceRule model used to interact with the recurrence rules collection in the database. + * @see EventResolvers - The type definition for the resolvers of the Event fields. + * */ export const recurrenceRule: EventResolvers["recurrenceRule"] = async ( diff --git a/src/resolvers/EventVolunteer/creator.ts b/src/resolvers/EventVolunteer/creator.ts index 3167c28cfd..59d13f2b57 100644 --- a/src/resolvers/EventVolunteer/creator.ts +++ b/src/resolvers/EventVolunteer/creator.ts @@ -1,6 +1,18 @@ import { User } from "../../models"; import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `creator` field of an `EventVolunteer`. + * + * This function retrieves the user who created a specific event volunteer. + * + * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer. + * + * @see User - The User model used to interact with the users collection in the database. + * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. + * + */ export const creator: EventVolunteerResolvers["creator"] = async (parent) => { return await User.findOne({ _id: parent.creatorId, diff --git a/src/resolvers/EventVolunteer/event.ts b/src/resolvers/EventVolunteer/event.ts index 61f67ac48e..438754aa91 100644 --- a/src/resolvers/EventVolunteer/event.ts +++ b/src/resolvers/EventVolunteer/event.ts @@ -1,6 +1,18 @@ import { Event } from "../../models"; import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `event` field of an `EventVolunteer`. + * + * This function retrieves the event associated with a specific event volunteer. + * + * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the event associated with it. + * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the event volunteer. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. + * + */ export const event: EventVolunteerResolvers["event"] = async (parent) => { return await Event.findOne({ _id: parent.eventId, diff --git a/src/resolvers/EventVolunteer/group.ts b/src/resolvers/EventVolunteer/group.ts index cfa636c375..4fa94b9ee6 100644 --- a/src/resolvers/EventVolunteer/group.ts +++ b/src/resolvers/EventVolunteer/group.ts @@ -1,6 +1,18 @@ import { EventVolunteerGroup } from "../../models"; import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `group` field of an `EventVolunteer`. + * + * This function retrieves the group associated with a specific event volunteer. + * + * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the group associated with it. + * @returns A promise that resolves to the group document found in the database. This document represents the group associated with the event volunteer. + * + * @see EventVolunteerGroup - The EventVolunteerGroup model used to interact with the event volunteer groups collection in the database. + * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. + * + */ export const group: EventVolunteerResolvers["group"] = async (parent) => { return await EventVolunteerGroup.findOne({ _id: parent.groupId, diff --git a/src/resolvers/EventVolunteer/user.ts b/src/resolvers/EventVolunteer/user.ts index c70d68ccf7..5059bd1dcb 100644 --- a/src/resolvers/EventVolunteer/user.ts +++ b/src/resolvers/EventVolunteer/user.ts @@ -1,6 +1,18 @@ import { User } from "../../models"; import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `user` field of an `EventVolunteer`. + * + * This function retrieves the user who created a specific event volunteer. + * + * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer. + * + * @see User - The User model used to interact with the users collection in the database. + * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. + * + */ export const user: EventVolunteerResolvers["user"] = async (parent) => { const result = await User.findOne({ _id: parent.userId, diff --git a/src/resolvers/EventVolunteerGroup/creator.ts b/src/resolvers/EventVolunteerGroup/creator.ts index a65494c188..7924de2115 100644 --- a/src/resolvers/EventVolunteerGroup/creator.ts +++ b/src/resolvers/EventVolunteerGroup/creator.ts @@ -1,6 +1,18 @@ import { User } from "../../models"; import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `creator` field of an `EventVolunteerGroup`. + * + * This function retrieves the user who created a specific event volunteer group. + * + * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer group. + * + * @see User - The User model used to interact with the users collection in the database. + * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. + * + */ export const creator: EventVolunteerGroupResolvers["creator"] = async ( parent, ) => { diff --git a/src/resolvers/EventVolunteerGroup/event.ts b/src/resolvers/EventVolunteerGroup/event.ts index 38e5eb8d13..b758191d0a 100644 --- a/src/resolvers/EventVolunteerGroup/event.ts +++ b/src/resolvers/EventVolunteerGroup/event.ts @@ -1,6 +1,18 @@ import { Event } from "../../models"; import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `event` field of an `EventVolunteerGroup`. + * + * This function retrieves the event associated with a specific event volunteer group. + * + * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the event associated with it. + * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the event volunteer group. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. + * + */ export const event: EventVolunteerGroupResolvers["event"] = async (parent) => { return await Event.findOne({ _id: parent.eventId, diff --git a/src/resolvers/EventVolunteerGroup/leader.ts b/src/resolvers/EventVolunteerGroup/leader.ts index 40ee89cdbd..93a47b3eab 100644 --- a/src/resolvers/EventVolunteerGroup/leader.ts +++ b/src/resolvers/EventVolunteerGroup/leader.ts @@ -2,6 +2,18 @@ import { User } from "../../models"; import type { InterfaceUser } from "../../models"; import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `leader` field of an `EventVolunteerGroup`. + * + * This function retrieves the user who is the leader of a specific event volunteer group. + * + * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the user who is the leader. + * @returns A promise that resolves to the user document found in the database. This document represents the user who is the leader of the event volunteer group. + * + * @see User - The User model used to interact with the users collection in the database. + * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. + * + */ export const leader: EventVolunteerGroupResolvers["leader"] = async ( parent, ) => { diff --git a/src/resolvers/Feedback/event.ts b/src/resolvers/Feedback/event.ts index a31fe3c8f0..7a7dd05f44 100644 --- a/src/resolvers/Feedback/event.ts +++ b/src/resolvers/Feedback/event.ts @@ -1,6 +1,18 @@ import type { FeedbackResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `event` field of a `Feedback`. + * + * This function retrieves the event associated with a specific feedback. + * + * @param parent - The parent object representing the feedback. It contains information about the feedback, including the ID of the event associated with it. + * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the feedback. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see FeedbackResolvers - The type definition for the resolvers of the Feedback fields. + * + */ export const event: FeedbackResolvers["event"] = async (parent) => { const result = await Event.findOne({ _id: parent.eventId, diff --git a/src/resolvers/Fund/campaigns.ts b/src/resolvers/Fund/campaigns.ts deleted file mode 100644 index 4c754c89cd..0000000000 --- a/src/resolvers/Fund/campaigns.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { FundraisingCampaign } from "../../models"; -import type { FundResolvers } from "../../types/generatedGraphQLTypes"; -/** - * This resolver function will fetch and return the campaigns assoicated with the fund from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all campaigns associated with the fund. - */ -export const campaigns: FundResolvers["campaigns"] = async (parent) => { - return await FundraisingCampaign.find({ - fundId: parent._id, - }).lean(); -}; diff --git a/src/resolvers/Fund/creator.ts b/src/resolvers/Fund/creator.ts index dd402564b0..bb8c9968a7 100644 --- a/src/resolvers/Fund/creator.ts +++ b/src/resolvers/Fund/creator.ts @@ -2,6 +2,18 @@ import type { FundResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; import { Types } from "mongoose"; +/** + * Resolver function for the `creator` field of a `Fund`. + * + * This function retrieves the user who created a specific fund. + * + * @param parent - The parent object representing the fund. It contains information about the fund, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the fund. + * + * @see User - The User model used to interact with the users collection in the database. + * @see FundResolvers - The type definition for the resolvers of the Fund fields. + * + */ export const creator: FundResolvers["creator"] = async (parent) => { return await User.findOne({ _id: new Types.ObjectId(parent.creatorId?.toString()), diff --git a/src/resolvers/Fund/index.ts b/src/resolvers/Fund/index.ts index 814edbf60f..f961853f05 100644 --- a/src/resolvers/Fund/index.ts +++ b/src/resolvers/Fund/index.ts @@ -1,7 +1,14 @@ import type { FundResolvers } from "../../types/generatedGraphQLTypes"; -import { campaigns } from "./campaigns"; import { creator } from "./creator"; + +/** + * Resolver functions for the fields of a `Fund`. + * + * These functions define how to resolve the fields of a `Fund` type. + * + * @see FundResolvers - The type definition for the resolvers of the Fund fields. + * + */ export const Fund: FundResolvers = { - campaigns, creator, }; diff --git a/src/resolvers/FundraisingCampagin/campaignPledges.ts b/src/resolvers/FundraisingCampagin/campaignPledges.ts deleted file mode 100644 index c477ee61cc..0000000000 --- a/src/resolvers/FundraisingCampagin/campaignPledges.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FundraisingCampaignPledge } from "../../models/FundraisingCampaignPledge"; -import type { FundraisingCampaignResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * This resolver function will fetch and return the pledges assoicated with the campaign from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all pledges associated with the campaign. - */ -export const pledges: FundraisingCampaignResolvers["pledges"] = async ( - parent, -) => { - return await FundraisingCampaignPledge.find({ - campaigns: parent._id, - }) - .populate("users") - .lean(); -}; diff --git a/src/resolvers/FundraisingCampagin/index.ts b/src/resolvers/FundraisingCampagin/index.ts deleted file mode 100644 index 21c848d717..0000000000 --- a/src/resolvers/FundraisingCampagin/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { FundraisingCampaignResolvers } from "../../types/generatedGraphQLTypes"; -import { pledges } from "./campaignPledges"; -import { fundId } from "./parentFund"; - -export const FundraisingCampaign: FundraisingCampaignResolvers = { - pledges, - fundId, -}; diff --git a/src/resolvers/FundraisingCampagin/parentFund.ts b/src/resolvers/FundraisingCampagin/parentFund.ts deleted file mode 100644 index 0e6178b118..0000000000 --- a/src/resolvers/FundraisingCampagin/parentFund.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Types } from "mongoose"; -import { Fund, type InterfaceFund } from "../../models"; -import type { FundraisingCampaignResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * This resolver will fetch the fund as a transaction from database. - * @param parent - A `fund` object. - * @returns A `fund` object. - */ -export const fundId: FundraisingCampaignResolvers["fundId"] = async ( - parent, -) => { - return (await Fund.findOne({ - _id: new Types.ObjectId(parent.fundId?.toString()), - })) as InterfaceFund; -}; diff --git a/src/resolvers/FundraisingCampaignPledge/index.ts b/src/resolvers/FundraisingCampaignPledge/index.ts index b4ef6411f6..2eac0d9f4a 100644 --- a/src/resolvers/FundraisingCampaignPledge/index.ts +++ b/src/resolvers/FundraisingCampaignPledge/index.ts @@ -1,5 +1,6 @@ import type { FundraisingCampaignPledgeResolvers } from "../../types/generatedGraphQLTypes"; import { users } from "./users"; + export const FundraisingCampaignPledge: FundraisingCampaignPledgeResolvers = { users, }; diff --git a/src/resolvers/FundraisingCampaignPledge/users.ts b/src/resolvers/FundraisingCampaignPledge/users.ts index bc9c580bd3..e0126555a4 100644 --- a/src/resolvers/FundraisingCampaignPledge/users.ts +++ b/src/resolvers/FundraisingCampaignPledge/users.ts @@ -2,9 +2,16 @@ import { User } from "../../models"; import type { FundraisingCampaignPledgeResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the list of users who have pledged to the Fundraising Campaign from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all users who have pledged to the Fundraising Campaign. + * Resolver function for the `users` field of a `FundraisingCampaignPledge`. + * + * This function retrieves the users who pledged to donate to a specific fundraising campaign. + * + * @param parent - The parent object representing the fundraising campaign pledge. It contains information about the fundraising campaign pledge, including the IDs of the users who pledged to donate. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who pledged to donate to the fundraising campaign. + * + * @see User - The User model used to interact with the users collection in the database. + * @see FundraisingCampaignPledgeResolvers - The type definition for the resolvers of the FundraisingCampaignPledge fields. + * */ export const users: FundraisingCampaignPledgeResolvers["users"] = async ( parent, diff --git a/src/resolvers/GroupChat/creator.ts b/src/resolvers/GroupChat/creator.ts index c64cc05699..ff7ea2cd51 100644 --- a/src/resolvers/GroupChat/creator.ts +++ b/src/resolvers/GroupChat/creator.ts @@ -1,9 +1,17 @@ import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; + /** - * This resolver function will fetch and return the Group Chat creator(User) from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the User data. + * Resolver function for the `creator` field of a `GroupChat`. + * + * This function retrieves the user who created a specific group chat. + * + * @param parent - The parent object representing the group chat. It contains information about the group chat, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the group chat. + * + * @see User - The User model used to interact with the users collection in the database. + * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. + * */ export const creator: GroupChatResolvers["creator"] = async (parent) => { return await User.findOne({ diff --git a/src/resolvers/GroupChat/messages.ts b/src/resolvers/GroupChat/messages.ts index a83ed2e22e..cb03954b94 100644 --- a/src/resolvers/GroupChat/messages.ts +++ b/src/resolvers/GroupChat/messages.ts @@ -1,9 +1,17 @@ import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; import { GroupChatMessage } from "../../models"; + /** - * This resolver function will fetch and return the list of group chat message from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of group chat messages. + * Resolver function for the `messages` field of a `GroupChat`. + * + * This function retrieves the messages associated with a specific group chat. + * + * @param parent - The parent object representing the group chat. It contains information about the group chat, including the IDs of the messages associated with it. + * @returns A promise that resolves to the message documents found in the database. These documents represent the messages associated with the group chat. + * + * @see GroupChatMessage - The GroupChatMessage model used to interact with the group chat messages collection in the database. + * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. + * */ export const messages: GroupChatResolvers["messages"] = async (parent) => { return await GroupChatMessage.find({ diff --git a/src/resolvers/GroupChat/organization.ts b/src/resolvers/GroupChat/organization.ts index d4d716a577..ab83f0f3b3 100644 --- a/src/resolvers/GroupChat/organization.ts +++ b/src/resolvers/GroupChat/organization.ts @@ -3,10 +3,18 @@ import { Organization } from "../../models"; import type { InterfaceOrganization } from "../../models"; import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; + /** - * This resolver function will fetch and return the organization for group chat from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains the organization data. + * Resolver function for the `organization` field of a `GroupChat`. + * + * This function retrieves the organization associated with a specific group chat. + * + * @param parent - The parent object representing the group chat. It contains information about the group chat, including the ID of the organization it is associated with. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the group chat. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. + * */ export const organization: GroupChatResolvers["organization"] = async ( parent, diff --git a/src/resolvers/GroupChat/users.ts b/src/resolvers/GroupChat/users.ts index 4b2c35b7c0..b4e0f363e1 100644 --- a/src/resolvers/GroupChat/users.ts +++ b/src/resolvers/GroupChat/users.ts @@ -1,9 +1,17 @@ import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; + /** - * This resolver function will fetch and return the list of all Users of the Group Chat from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains the User data. + * Resolver function for the `users` field of a `GroupChat`. + * + * This function retrieves the users who are members of a specific group chat. + * + * @param parent - The parent object representing the group chat. It contains information about the group chat, including the IDs of the users who are members of it. + * @returns A promise that resolves to the user documents found in the database. These documents represent the users who are members of the group chat. + * + * @see User - The User model used to interact with the users collection in the database. + * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. + * */ export const users: GroupChatResolvers["users"] = async (parent) => { return await User.find({ diff --git a/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts b/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts index 9f4881b192..c9e2ec6a08 100644 --- a/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts +++ b/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts @@ -2,10 +2,18 @@ import type { GroupChatMessageResolvers } from "../../types/generatedGraphQLType import { GroupChat } from "../../models"; import { errors, requestContext } from "../../libraries"; import { CHAT_NOT_FOUND_ERROR } from "../../constants"; + /** - * This resolver method will retrieve and return from the database the Group chat to which the specified message belongs. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the Group chat data. + * Resolver function for the `groupChatMessageBelongsTo` field of a `GroupChatMessage`. + * + * This function retrieves the group chat to which a specific group chat message belongs. + * + * @param parent - The parent object representing the group chat message. It contains information about the group chat message, including the ID of the group chat to which it belongs. + * @returns A promise that resolves to the group chat document found in the database. This document represents the group chat to which the group chat message belongs. + * + * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. + * @see GroupChatMessageResolvers - The type definition for the resolvers of the GroupChatMessage fields. + * */ export const groupChatMessageBelongsTo: GroupChatMessageResolvers["groupChatMessageBelongsTo"] = async (parent) => { diff --git a/src/resolvers/GroupChatMessage/sender.ts b/src/resolvers/GroupChatMessage/sender.ts index 0c647b2575..9497a05f23 100644 --- a/src/resolvers/GroupChatMessage/sender.ts +++ b/src/resolvers/GroupChatMessage/sender.ts @@ -2,10 +2,18 @@ import type { GroupChatMessageResolvers } from "../../types/generatedGraphQLType import { User } from "../../models"; import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver function will fetch and return the send of the group chat message from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that will contain the User data. + * Resolver function for the `sender` field of a `GroupChatMessage`. + * + * This function retrieves the user who sent a specific group chat message. + * + * @param parent - The parent object representing the group chat message. It contains information about the group chat message, including the ID of the user who sent it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who sent the group chat message. + * + * @see User - The User model used to interact with the users collection in the database. + * @see GroupChatMessageResolvers - The type definition for the resolvers of the GroupChatMessage fields. + * */ export const sender: GroupChatMessageResolvers["sender"] = async (parent) => { const result = await User.findOne({ diff --git a/src/resolvers/MembershipRequest/organization.ts b/src/resolvers/MembershipRequest/organization.ts index 2aa150c33f..10b9ef9503 100644 --- a/src/resolvers/MembershipRequest/organization.ts +++ b/src/resolvers/MembershipRequest/organization.ts @@ -2,10 +2,18 @@ import type { MembershipRequestResolvers } from "../../types/generatedGraphQLTyp import { Organization } from "../../models"; import { ORGANIZATION_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver function will get and return the organisation from the database for which a membership request was sent. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the Organization data. + * Resolver function for the `organization` field of a `MembershipRequest`. + * + * This function retrieves the organization associated with a specific membership request. + * + * @param parent - The parent object representing the membership request. It contains information about the membership request, including the ID of the organization it is associated with. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the membership request. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see MembershipRequestResolvers - The type definition for the resolvers of the MembershipRequest fields. + * */ export const organization: MembershipRequestResolvers["organization"] = async ( parent, diff --git a/src/resolvers/MembershipRequest/user.ts b/src/resolvers/MembershipRequest/user.ts index 43879c4126..ac33f80c17 100644 --- a/src/resolvers/MembershipRequest/user.ts +++ b/src/resolvers/MembershipRequest/user.ts @@ -2,10 +2,18 @@ import type { MembershipRequestResolvers } from "../../types/generatedGraphQLTyp import { User } from "../../models"; import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; + /** - * This resolver function will retrieve and return the user who sent the membership request from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the User data. + * Resolver function for the `user` field of a `MembershipRequest`. + * + * This function retrieves the user who made a specific membership request. + * + * @param parent - The parent object representing the membership request. It contains information about the membership request, including the ID of the user who made it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who made the membership request. + * + * @see User - The User model used to interact with the users collection in the database. + * @see MembershipRequestResolvers - The type definition for the resolvers of the MembershipRequest fields. + * */ export const user: MembershipRequestResolvers["user"] = async (parent) => { const result = await User.findOne({ diff --git a/src/resolvers/Mutation/addEventAttendee.ts b/src/resolvers/Mutation/addEventAttendee.ts index 816635f522..8c4242c484 100644 --- a/src/resolvers/Mutation/addEventAttendee.ts +++ b/src/resolvers/Mutation/addEventAttendee.ts @@ -21,6 +21,33 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Mutation resolver function to add a user as an attendee to an event. + * + * This function performs the following actions: + * 1. Retrieves the current user from the cache or database. + * 2. Retrieves the current user's app profile from the cache or database. + * 3. Retrieves the event from the cache or database. + * 4. Checks if the user making the request is an admin of the event or a super admin. + * 5. Validates that the user to be added as an attendee exists and is not already registered for the event. + * 6. Checks if the user to be added is a member of the organization hosting the event. + * 7. Adds the user as an attendee to the event if all checks pass. + * + * @param _parent - The parent object for the mutation. Typically, this is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.eventId`: The ID of the event to which the user will be added as an attendee. + * - `data.userId`: The ID of the user to be added as an attendee. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the user document representing the user added as an attendee. + * + * @see User - The User model used to interact with the users collection in the database. + * @see AppUserProfile - The AppUserProfile model used to interact with the app user profiles collection in the database. + * @see Event - The Event model used to interact with the events collection in the database. + * @see EventAttendee - The EventAttendee model used to manage event attendee registrations. + * @see MutationResolvers - The type definition for the mutation resolvers. + */ export const addEventAttendee: MutationResolvers["addEventAttendee"] = async ( _parent, args, diff --git a/src/resolvers/Mutation/addFeedback.ts b/src/resolvers/Mutation/addFeedback.ts index 8923e63dfb..0ed037ae01 100644 --- a/src/resolvers/Mutation/addFeedback.ts +++ b/src/resolvers/Mutation/addFeedback.ts @@ -8,6 +8,32 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { errors, requestContext } from "../../libraries"; import { Event, EventAttendee, CheckIn, Feedback } from "../../models"; +/** + * Mutation resolver function to add feedback for an event. + * + * This function pcerforms the following ations: + * 1. Checks if the specified event exists. + * 2. Retrieves the event attendee record for the current user and event. + * 3. Checks if the user is registered for the event and if they have checked in. + * 4. Ensures the user has not already submitted feedback for the event. + * 5. Updates the check-in record to mark feedback as submitted. + * 6. Creates and saves a new feedback entry. + * + * @param _parent - The parent object for the mutation. Typically, this is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.eventId`: The ID of the event for which feedback is being submitted. + * - `data.feedback`: The feedback content to be submitted. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the newly created feedback document. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see EventAttendee - The EventAttendee model used to manage event attendee records. + * @see CheckIn - The CheckIn model used to manage check-in records. + * @see Feedback - The Feedback model used to create and manage feedback entries. + * @see MutationResolvers - The type definition for the mutation resolvers. + */ export const addFeedback: MutationResolvers["addFeedback"] = async ( _parent, args, diff --git a/src/resolvers/Mutation/addLanguageTranslation.ts b/src/resolvers/Mutation/addLanguageTranslation.ts index 96077de8b2..e00ae66b81 100644 --- a/src/resolvers/Mutation/addLanguageTranslation.ts +++ b/src/resolvers/Mutation/addLanguageTranslation.ts @@ -3,14 +3,32 @@ import { errors, requestContext } from "../../libraries"; import { Language } from "../../models"; import type { InterfaceLanguage } from "../../models"; import { TRANSLATION_ALREADY_PRESENT_ERROR } from "../../constants"; + /** - * This function adds language translation. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @remarks The following checks are done: - * 1. If the language exists - * 2. If the translation already exists. - * @returns Updated langauge + * Mutation resolver function to add a translation for a language. + * + * This function performs the following actions: + * 1. Checks if the language with the provided English value exists in the database. + * 2. If the language exists, checks if the translation for the specified language code already exists. + * 3. If the translation already exists, throws a conflict error. + * 4. If the translation does not exist, updates the language with the new translation. + * 5. If the language does not exist, creates a new language entry with the provided translation. + * + * @param _parent - The parent object for the mutation. Typically, this is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.en_value`: The English value of the language to which the translation is being added. + * - `data.translation_lang_code`: The language code for the translation being added. + * - `data.translation_value`: The translation value to be added. + * + * @returns A promise that resolves to the updated or newly created language document. + * + * @see Language - The Language model used to interact with the languages collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * + * @remarks + * The function uses the `findOne` method to locate an existing language entry by its English value. + * If the language exists, it checks the existing translations to prevent duplicate entries. + * If the language does not exist, a nsnew entry is created with the provided tralation. */ export const addLanguageTranslation: MutationResolvers["addLanguageTranslation"] = async (_parent, args) => { diff --git a/src/resolvers/Mutation/addOrganizationCustomField.ts b/src/resolvers/Mutation/addOrganizationCustomField.ts index 7a89de5f1e..88e3a62415 100644 --- a/src/resolvers/Mutation/addOrganizationCustomField.ts +++ b/src/resolvers/Mutation/addOrganizationCustomField.ts @@ -21,19 +21,28 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables an admin to add an organization colleciton field. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the user has appProfile - * 3. If the organization exists. - * 4. If the user is an admin for the organization. - * 5. If the required name and value was provided for the new custom field - * @returns Newly Added Custom Field. + * Mutation resolver to add a custom field to an organization. + * + * This function allows an admin to add a new custom field to the collection of fields for a specified organization. It performs several checks: + * + * 1. Verifies the existence of the user. + * 2. Checks if the user has an application profile. + * 3. Confirms that the organization exists. + * 4. Ensures that the user is an admin for the organization or has super admin privileges. + * 5. Validates that the name and type of the custom field are provided. + * + * If any of these conditions are not met, appropriate errors are thrown. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `organizationId`: The ID of the organization to which the custom field will be added. + * - `name`: The name of the new custom field. + * - `type`: The type of the new custom field. + * @param context - The context of the entire application, containing user information and other context-specific data. + * + * @returns A promise that resolves to the newly added custom field object. + * */ - export const addOrganizationCustomField: MutationResolvers["addOrganizationCustomField"] = async (_parent, args, context) => { let currentUser: InterfaceUser | null; diff --git a/src/resolvers/Mutation/addOrganizationImage.ts b/src/resolvers/Mutation/addOrganizationImage.ts index 1b3c05ba4e..b167ab0036 100644 --- a/src/resolvers/Mutation/addOrganizationImage.ts +++ b/src/resolvers/Mutation/addOrganizationImage.ts @@ -10,15 +10,36 @@ import { adminCheck } from "../../utilities"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; /** - * This function adds Organization Image. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2. If the organization exists - * 3. If the user trying to add the image is an admin of organization - * @returns Updated Organization + * Mutation resolver function to add or update an organization's image. + * + * This function performs the following actions: + * 1. Retrieves the organization from the cache or database based on the provided `organizationId`. + * 2. Checks if the organization exists. If not, throws a not found error. + * 3. Verifies if the current user is an admin of the organization. + * 4. Uploads the provided image file and updates the organization's image field with the new file name. + * 5. Updates the organization document in the database with the new image information. + * 6. Caches the updated organization data. + * + * @param _parent - The parent object for the mutation. Typically, this is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `organizationId`: The ID of the organization to which the image is being added or updated. + * - `file`: The encoded image file to be uploaded. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the updated organization document with the new image. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see adminCheck - Utility function to verify if a user is an admin of an organization. + * @see uploadEncodedImage - Utility function to handle the upload of an encoded image file. + * @see cacheOrganizations - Service function to cache the updated organization data. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * + * @remarks + * The function uses the `findOrganizationsInCache` method to first attempt to retrieve the organization from the cache. + * If the organization is not found in the cache, it queries the database. + * It then verifies the user's admin status and performs the image upload before updating the organization's image field. */ export const addOrganizationImage: MutationResolvers["addOrganizationImage"] = async (_parent, args, context) => { diff --git a/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts b/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts index 0ea4a731c8..574c0d16e9 100644 --- a/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts +++ b/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts @@ -15,19 +15,29 @@ import { import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -/** - * This function adds campaign pledge to campaign. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2. If the pledge exists - * 3. If the campaign exists - * @returns Updated pledge +/** + * Mutation resolver to add a pledge to a fundraising campaign. + * + * This function adds a specified pledge to a fundraising campaign. It performs several checks: + * + * 1. Verifies that the current user exists. + * 2. Confirms that the pledge exists. + * 3. Checks that the campaign exists. + * 4. Ensures the user has made the pledge. + * 5. Verifies that the campaign is not already associated with the pledge. + * + * If any of these conditions are not met, appropriate errors are thrown. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `pledgeId`: The ID of the pledge to be added. + * - `campaignId`: The ID of the campaign to which the pledge will be added. + * @param context - The context of the entire application, containing user information and other context-specific data. + * + * @returns A promise that resolves to the updated pledge object. + * */ - export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundraisingCampaign"] = async ( _parent, @@ -48,7 +58,8 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr await cacheUsers([currentUser]); } } - // Checks whether currentUser exists. + + // Checks whether the current user exists. if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -56,11 +67,12 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr USER_NOT_FOUND_ERROR.PARAM, ); } + const pledge = await FundraisingCampaignPledge.findOne({ _id: args.pledgeId, }).lean(); - // Checks whether pledge exists. + // Checks whether the pledge exists. if (!pledge) { throw new errors.NotFoundError( requestContext.translate( @@ -70,11 +82,12 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR.PARAM, ); } + const campaign = await FundraisingCampaign.findOne({ _id: args.campaignId, }).lean(); - // Checks whether campaign exists. + // Checks whether the campaign exists. if (!campaign) { throw new errors.NotFoundError( requestContext.translate(FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.MESSAGE), @@ -82,6 +95,7 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.PARAM, ); } + // Checks whether the user has made the pledge. const pledgeUserIds = pledge.users.map((id) => id?.toString()); if (!pledgeUserIds.includes(context.userId)) { @@ -91,6 +105,7 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr USER_NOT_MADE_PLEDGE_ERROR.PARAM, ); } + // Checks whether the campaign is already added to the pledge. const pledgeCampaignIds = pledge.campaigns.map((id) => id?.toString()); if (pledgeCampaignIds.includes(args.campaignId)) { @@ -100,7 +115,8 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr FUNDRAISING_CAMPAIGN_ALREADY_ADDED.PARAM, ); } - // Add the campaign to the pledge + + // Add the campaign to the pledge. const updatedPledge = await FundraisingCampaignPledge.findOneAndUpdate( { _id: args.pledgeId, @@ -111,7 +127,7 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr { new: true }, ); - // Add the pledge to the campaign + // Add the pledge to the campaign. await FundraisingCampaign.updateOne( { _id: args.campaignId, @@ -120,5 +136,6 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr $push: { pledges: args.pledgeId }, }, ); + return updatedPledge as InterfaceFundraisingCampaignPledges; }; diff --git a/src/resolvers/Mutation/addUserCustomData.ts b/src/resolvers/Mutation/addUserCustomData.ts index 87b92ad50e..45f16ca141 100644 --- a/src/resolvers/Mutation/addUserCustomData.ts +++ b/src/resolvers/Mutation/addUserCustomData.ts @@ -11,14 +11,25 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables a user to add data for a custom field for a joined organization. - * @param _parent - parent of the current request - * @param args - payload provided with the request - * @param context - context of the entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists. - * @returns Newly Added User Custom Field. + * Mutation resolver to add or update custom data for a user within a joined organization. + * + * This function allows a user to add or update a custom field with a name and value for an organization + * they are a part of. It performs several checks and operations: + * + * 1. Validates that the user exists. + * 2. Verifies that the organization exists. + * 3. Checks if user custom data for the given organization already exists. + * 4. If it exists, updates the custom field; if not, creates a new entry. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `organizationId`: The ID of the organization for which custom data is being added. + * - `dataName`: The name of the custom data field. + * - `dataValue`: The value of the custom data field. + * @param context - The context of the entire application, including user information and other context-specific data. + * + * @returns A promise that resolves to the newly added or updated user custom data object. + * */ export const addUserCustomData: MutationResolvers["addUserCustomData"] = async ( _parent, diff --git a/src/resolvers/Mutation/addUserImage.ts b/src/resolvers/Mutation/addUserImage.ts index 5c06d83a71..f034cb4303 100644 --- a/src/resolvers/Mutation/addUserImage.ts +++ b/src/resolvers/Mutation/addUserImage.ts @@ -6,14 +6,35 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; + /** - * This function adds User Image. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * @returns Updated User + * Mutation resolver function to add or update a user's profile image. + * + * This function performs the following actions: + * 1. Retrieves the current user from the cache or database based on the `userId` from the context. + * 2. Checks if the current user exists. If not, throws a not found error. + * 3. Uploads the provided encoded image file and updates the user's profile image with the new file path. + * 4. Updates the user document in the database with the new image information. + * 5. Caches the updated user data. + * + * @param _parent - The parent object for the mutation. Typically, this is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `file`: The encoded image file to be uploaded. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the updated user document with the new image. + * + * @see User - The User model used to interact with the users collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see uploadEncodedImage - Utility function to handle the upload of an encoded image file. + * @see cacheUsers - Service function to cache the updated user data. + * @see findUserInCache - Service function to retrieve users from cache. + * + * @remarks + * The function first attempts to retrieve the user from the cache using `findUserInCache`. + * If the user is not found in the cache, it queries the database. + * It then performs the image upload and updates the user's profile image before saving the changes to the database. */ export const addUserImage: MutationResolvers["addUserImage"] = async ( _parent, diff --git a/src/resolvers/Mutation/addUserToGroupChat.ts b/src/resolvers/Mutation/addUserToGroupChat.ts index ff0953cdb6..d71535f578 100644 --- a/src/resolvers/Mutation/addUserToGroupChat.ts +++ b/src/resolvers/Mutation/addUserToGroupChat.ts @@ -12,18 +12,34 @@ import { findOrganizationsInCache } from "../../services/OrganizationCache/findO import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { adminCheck } from "../../utilities"; import type { InterfaceGroupChat } from "../../models"; + /** - * This function adds user to group chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the group chat exists - * 2. If the organization exists - * 3. If the user trying to add the user is an admin of organization - * 4. If the user exists - * 5. If the user is already a member of the chat - * @returns Updated Group chat + * Mutation resolver function to add a user to a group chat. + * + * This function performs the following actions: + * 1. Checks if the group chat specified by `args.chatId` exists. + * 2. Checks if the organization associated with the group chat exists. + * 3. Verifies that the current user (identified by `context.userId`) is an admin of the organization. + * 4. Confirms that the user to be added (specified by `args.userId`) exists. + * 5. Ensures that the user is not already a member of the group chat. + * 6. Adds the user to the list of users in the group chat and returns the updated group chat. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `chatId`: The ID of the group chat to which the user will be added. + * - `userId`: The ID of the user to be added to the group chat. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the updated group chat document with the new user added. + * + * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see User - The User model used to interact with the users collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see adminCheck - Utility function to check if the current user is an admin of the organization. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * @see cacheOrganizations - Service function to cache updated organization data. */ export const addUserToGroupChat: MutationResolvers["addUserToGroupChat"] = async (_parent, args, context) => { diff --git a/src/resolvers/Mutation/addUserToUserFamily.ts b/src/resolvers/Mutation/addUserToUserFamily.ts index 39eab3dfae..1c52f462d7 100644 --- a/src/resolvers/Mutation/addUserToUserFamily.ts +++ b/src/resolvers/Mutation/addUserToUserFamily.ts @@ -13,17 +13,25 @@ import { } from "../../constants"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import { cacheUsers } from "../../services/UserCache/cacheUser"; + /** - * This function adds user to the family. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of the entire application - * @remarks The following checks are done: - * 1. If the family exists - * 2. If the user exists - * 3. If the user is already member of the family - * 4. If the user is admin of the user Family - * @returns Updated family + * Adds a user to a user family. + * + * This function allows an admin to add a user to a specific user family. It performs several checks: + * + * 1. Verifies if the user family exists. + * 2. Checks if the user exists. + * 3. Confirms that the user is not already a member of the family. + * 4. Ensures that the current user is an admin of the user family. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `familyId`: The ID of the user family to which the user will be added. + * - `userId`: The ID of the user to be added to the user family. + * @param context - The context of the entire application, including user information and other context-specific data. + * + * @returns A promise that resolves to the updated user family object. + * */ export const addUserToUserFamily: MutationResolvers["addUserToUserFamily"] = async (_parent, args, context) => { @@ -51,7 +59,7 @@ export const addUserToUserFamily: MutationResolvers["addUserToUserFamily"] = ); } - //check wheather family exists + // Check whether family exists. if (!userFamily) { throw new errors.NotFoundError( requestContext.translate(USER_FAMILY_NOT_FOUND_ERROR.MESSAGE), @@ -60,14 +68,14 @@ export const addUserToUserFamily: MutationResolvers["addUserToUserFamily"] = ); } - //check whether user is admin of the family + // Check whether user is an admin of the family. await adminCheck(currentUser?._id, userFamily); const isUserMemberOfUserFamily = userFamily.users.some((user) => user.equals(args.userId), ); - // Checks whether user with _id === args.userId is already a member of Family. + // Checks whether user with _id === args.userId is already a member of the family. if (isUserMemberOfUserFamily) { throw new errors.ConflictError( requestContext.translate(USER_ALREADY_MEMBER_ERROR.MESSAGE), @@ -76,7 +84,7 @@ export const addUserToUserFamily: MutationResolvers["addUserToUserFamily"] = ); } - // Adds args.userId to users lists on family group and return the updated family. + // Adds args.userId to the users list in the user family and returns the updated family. const updatedFamily = await UserFamily.findOneAndUpdate( { _id: args.familyId, diff --git a/src/resolvers/Mutation/adminRemoveGroup.ts b/src/resolvers/Mutation/adminRemoveGroup.ts index 67dcb37e51..972f6bca06 100644 --- a/src/resolvers/Mutation/adminRemoveGroup.ts +++ b/src/resolvers/Mutation/adminRemoveGroup.ts @@ -10,19 +10,34 @@ import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrgani import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { adminCheck } from "../../utilities"; + /** - * This function enables an admin to remove a group. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the group chat exists - * 2. If the organization exists - * 3. If the user exists - * 4. If the user is an admin of organization - * @returns Deleted group chat + * Mutation resolver function to remove a group chat. + * + * This function performs the following actions: + * 1. Checks if the group chat specified by `args.groupId` exists. + * 2. Verifies that the organization associated with the group chat exists. + * 3. Ensures that the current user (identified by `context.userId`) exists. + * 4. Checks that the current user is authorized as an admin of the organization. + * 5. Deletes the group chat from the database. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `groupId`: The ID of the group chat to be removed. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the deleted group chat document. + * + * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see User - The User model used to interact with the users collection in the database. + * @see AppUserProfile - The AppUserProfile model used to retrieve the user's profile information. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see adminCheck - Utility function to check if the current user is an admin of the organization. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * @see cacheOrganizations - Service function to cache updated organization data. */ - export const adminRemoveGroup: MutationResolvers["adminRemoveGroup"] = async ( _parent, args, diff --git a/src/resolvers/Mutation/blockPluginCreationBySuperadmin.ts b/src/resolvers/Mutation/blockPluginCreationBySuperadmin.ts index 2aacf1a0b3..670bb918c2 100644 --- a/src/resolvers/Mutation/blockPluginCreationBySuperadmin.ts +++ b/src/resolvers/Mutation/blockPluginCreationBySuperadmin.ts @@ -11,16 +11,25 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { superAdminCheck } from "../../utilities"; + /** - * This function enables an admin to create block plugin. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2.If the user has appUserProfile - * 2. If the user is the SUPERADMIN of organization - * @returns Deleted updated user + * Allows a superadmin to enable or disable plugin creation for a specific user. + * + * This function performs several checks: + * + * 1. Verifies if the current user exists. + * 2. Ensures that the current user has an associated app user profile. + * 3. Confirms that the current user is a superadmin. + * 4. Checks if the target user exists and updates their `pluginCreationAllowed` field based on the provided value. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `userId`: The ID of the user whose plugin creation permissions are being modified. + * - `blockUser`: A boolean indicating whether to block (`true`) or allow (`false`) plugin creation for the user. + * @param context - The context of the entire application, including user information and other context-specific data. + * + * @returns A promise that resolves to the updated user app profile object with the new `pluginCreationAllowed` value. + * */ export const blockPluginCreationBySuperadmin: MutationResolvers["blockPluginCreationBySuperadmin"] = async (_parent, args, context) => { @@ -68,6 +77,7 @@ export const blockPluginCreationBySuperadmin: MutationResolvers["blockPluginCrea // Checks whether currentUser is a SUPERADMIN. superAdminCheck(currentUserAppProfile as InterfaceAppUserProfile); + const userAppProfile = await AppUserProfile.findOne({ userId: args.userId, }).lean(); @@ -78,9 +88,10 @@ export const blockPluginCreationBySuperadmin: MutationResolvers["blockPluginCrea USER_NOT_FOUND_ERROR.PARAM, ); } + /* - Sets pluginCreationAllowed field on document of appUserProfile with _id === args.userId - to !args.blockUser and returns the updated user. + Sets the pluginCreationAllowed field on the document of the appUserProfile with _id === args.userId + to !args.blockUser and returns the updated user profile. */ return (await AppUserProfile.findOneAndUpdate( { diff --git a/src/resolvers/Mutation/blockUser.ts b/src/resolvers/Mutation/blockUser.ts index b23f6c14d3..cfe0104d5b 100644 --- a/src/resolvers/Mutation/blockUser.ts +++ b/src/resolvers/Mutation/blockUser.ts @@ -13,17 +13,34 @@ import { findOrganizationsInCache } from "../../services/OrganizationCache/findO import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { adminCheck } from "../../utilities"; import type { InterfaceUser } from "../../models"; + /** - * This function enables blocking a user. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the organization exists - * 2. If the user exists - * 3. If the user is an admin of organization - * 4. If the user to be blocked is already blocked by the organization - * @returns Deleted updated user + * Mutation resolver function to block a user from an organization. + * + * This function performs the following actions: + * 1. Verifies that the organization specified by `args.organizationId` exists. + * 2. Ensures that the user specified by `args.userId` exists. + * 3. Checks if the user attempting to block the user is an admin of the organization. + * 4. Verifies if the user to be blocked is currently a member of the organization. + * 5. Ensures that the user is not attempting to block themselves. + * 6. Blocks the user by adding them to the organization's `blockedUsers` list and removing them from the `members` list. + * 7. Updates the user's document to reflect that they have been blocked by the organization, and removes the organization from their `joinedOrganizations` list. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `organizationId`: The ID of the organization from which the user is to be blocked. + * - `userId`: The ID of the user to be blocked. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the updated user document after blocking. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see User - The User model used to interact with the users collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see adminCheck - Utility function to check if the current user is an admin of the organization. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * @see cacheOrganizations - Service function to cache updated organization data. */ export const blockUser: MutationResolvers["blockUser"] = async ( _parent, @@ -110,7 +127,7 @@ export const blockUser: MutationResolvers["blockUser"] = async ( /* Adds args.userId to blockedUsers list on organization's document. - Removes args.userId from the organization's members list + Removes args.userId from the organization's members list. */ const updatedOrganization = await Organization.findOneAndUpdate( { @@ -134,9 +151,8 @@ export const blockUser: MutationResolvers["blockUser"] = async ( } /* - Adds organization._id to organizationsBlockedBy list on user's document - with _id === args.userId and returns the updated user. - Remove organization's id from joinedOrganizations list on args.userId. + Adds organization._id to organizationsBlockedBy list on user's document. + Removes organization._id from joinedOrganizations list on user's document. */ return (await User.findOneAndUpdate( { diff --git a/src/resolvers/Mutation/cancelMembershipRequest.ts b/src/resolvers/Mutation/cancelMembershipRequest.ts index d348d23fcf..4408d0b80f 100644 --- a/src/resolvers/Mutation/cancelMembershipRequest.ts +++ b/src/resolvers/Mutation/cancelMembershipRequest.ts @@ -12,17 +12,35 @@ import { findOrganizationsInCache } from "../../services/OrganizationCache/findO import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This function enables to cancel membership request. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the membership request exists - * 2. If the organization exists - * 3. If the user exists - * 4. If the user is the creator of the request - * @returns Deleted membership request + * Mutation resolver function to cancel a membership request. + * + * This function performs the following actions: + * 1. Verifies that the membership request specified by `args.membershipRequestId` exists. + * 2. Ensures that the organization associated with the membership request exists. + * 3. Confirms that the user specified by `context.userId` exists. + * 4. Checks if the current user is the creator of the membership request. + * 5. Deletes the membership request. + * 6. Updates the organization document to remove the membership request from its `membershipRequests` list. + * 7. Updates the user's document to remove the membership request from their `membershipRequests` list. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `membershipRequestId`: The ID of the membership request to be canceled. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the deleted membership request. + * + * @see MembershipRequest - The MembershipRequest model used to interact with the membership requests collection in the database. + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see User - The User model used to interact with the users collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * @see cacheOrganizations - Service function to cache updated organization data. + * @see findUserInCache - Service function to retrieve users from cache. + * @see cacheUsers - Service function to cache updated user data. */ export const cancelMembershipRequest: MutationResolvers["cancelMembershipRequest"] = async (_parent, args, context) => { diff --git a/src/resolvers/Mutation/createActionItem.ts b/src/resolvers/Mutation/createActionItem.ts index dc93a6941c..8491873e63 100644 --- a/src/resolvers/Mutation/createActionItem.ts +++ b/src/resolvers/Mutation/createActionItem.ts @@ -30,23 +30,31 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables to create an action item. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2.If the user has appUserProfile - * 3. If the asignee exists - * 4. If the actionItemCategory exists - * 5. If the actionItemCategory is disabled - * 6. If the asignee is a member of the organization - * 7. If the user is a member of the organization - * 8. If the event exists (if action item related to an event) - * 9. If the user is authorized. - * @returns Created action item + * Creates a new action item and assigns it to a user. + * + * This function performs several checks: + * + * 1. Verifies if the current user exists. + * 2. Ensures that the current user has an associated app user profile. + * 3. Checks if the assignee exists. + * 4. Validates if the action item category exists and is not disabled. + * 5. Confirms that the assignee is a member of the organization associated with the action item category. + * 6. If the action item is related to an event, checks if the event exists and whether the current user is an admin of that event. + * 7. Verifies if the current user is an admin of the organization or a superadmin. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `data`: An object containing: + * - `assigneeId`: The ID of the user to whom the action item is assigned. + * - `preCompletionNotes`: Notes to be added before the action item is completed. + * - `dueDate`: The due date for the action item. + * - `eventId` (optional): The ID of the event associated with the action item. + * - `actionItemCategoryId`: The ID of the action item category. + * @param context - The context of the entire application, including user information and other context-specific data. + * + * @returns A promise that resolves to the created action item object. + * */ - export const createActionItem: MutationResolvers["createActionItem"] = async ( _parent, args, @@ -63,6 +71,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( await cacheUsers([currentUser]); } } + // Checks whether currentUser with _id === context.userId exists. if (currentUser === null) { throw new errors.NotFoundError( @@ -71,6 +80,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( USER_NOT_FOUND_ERROR.PARAM, ); } + let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), @@ -84,6 +94,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( await cacheAppUserProfile([currentUserAppProfile]); } } + if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -96,7 +107,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( _id: args.data.assigneeId, }); - // Checks whether the asignee exists. + // Checks whether the assignee exists. if (assignee === null) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -109,7 +120,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( _id: args.actionItemCategoryId, }).lean(); - // Checks if the actionItemCategory exists + // Checks if the actionItemCategory exists. if (!actionItemCategory) { throw new errors.NotFoundError( requestContext.translate(ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR.MESSAGE), @@ -118,7 +129,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); } - // Checks if the actionItemCategory is disabled + // Checks if the actionItemCategory is disabled. if (actionItemCategory.isDisabled) { throw new errors.ConflictError( requestContext.translate(ACTION_ITEM_CATEGORY_IS_DISABLED.MESSAGE), @@ -173,7 +184,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); } - // Checks if the currUser is an admin of the event + // Checks if the currUser is an admin of the event. currentUserIsEventAdmin = currEvent.admins.some( (admin) => admin === context.userID || @@ -181,7 +192,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); } - // Checks if the currUser is an admin of the organization + // Checks if the currentUser is an admin of the organization. const currentUserIsOrgAdmin = currentUserAppProfile.adminFor.some( (organizationId) => (organizationId && @@ -191,7 +202,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ), ); - // Checks whether currentUser with _id === context.userId is authorized for the operation. + // Checks whether the currentUser is authorized for the operation. if ( currentUserIsEventAdmin === false && currentUserIsOrgAdmin === false && @@ -204,7 +215,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); } - // Creates new action item. + // Creates and returns the new action item. const createActionItem = await ActionItem.create({ assigneeId: args.data.assigneeId, assignerId: context.userId, @@ -215,6 +226,5 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( creatorId: context.userId, }); - // Returns created action item. return createActionItem.toObject(); }; diff --git a/src/resolvers/Mutation/createActionItemCategory.ts b/src/resolvers/Mutation/createActionItemCategory.ts index 462ff579d8..711f22449e 100644 --- a/src/resolvers/Mutation/createActionItemCategory.ts +++ b/src/resolvers/Mutation/createActionItemCategory.ts @@ -15,18 +15,34 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import { adminCheck } from "../../utilities"; /** - * This function enables to create an ActionItemCategory. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the User exists - * 2. If the Organization exists - * 3. Is the User is Authorized - * 4. If the action item category already exists - * @returns Created ActionItemCategory + * Mutation resolver function to create a new ActionItemCategory. + * + * This function performs the following actions: + * 1. Verifies that the current user, identified by `context.userId`, exists. + * 2. Ensures that the organization specified by `args.organizationId` exists. + * 3. Checks if the current user is authorized to perform the operation (must be an admin). + * 4. Checks if an ActionItemCategory with the provided name already exists for the specified organization. + * 5. Creates a new ActionItemCategory if no conflicts are found. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `name`: The name of the ActionItemCategory to be created. + * - `organizationId`: The ID of the organization where the ActionItemCategory will be created. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * + * @returns A promise that resolves to the created ActionItemCategory. + * + * @see ActionItemCategory - The ActionItemCategory model used to interact with the ActionItemCategory collection in the database. + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see User - The User model used to interact with the users collection in the database. + * @see MutationResolvers - The type definition for the mutation resolvers. + * @see findOrganizationsInCache - Service function to retrieve organizations from cache. + * @see cacheOrganizations - Service function to cache updated organization data. + * @see findUserInCache - Service function to retrieve users from cache. + * @see cacheUsers - Service function to cache updated user data. + * @see adminCheck - Utility function to check if a user is an admin of an organization. */ - export const createActionItemCategory: MutationResolvers["createActionItemCategory"] = async (_parent, args, context) => { let currentUser: InterfaceUser | null; diff --git a/src/resolvers/Mutation/createAdmin.ts b/src/resolvers/Mutation/createAdmin.ts index 4b0b3e1bf1..22f9df9fca 100644 --- a/src/resolvers/Mutation/createAdmin.ts +++ b/src/resolvers/Mutation/createAdmin.ts @@ -15,19 +15,34 @@ import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrgani import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { superAdminCheck } from "../../utilities"; + /** - * This function enables to create an admin for an organization. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the organization exists - * 2. If the user has appUserProfile - * 3. If the current user is the creator of the organization - * 4. If the user exists - * 5. If the user is a member of the organization - * 6. If the user is already an admin of the organization - * @returns Updated appUserProfile + * Creates an admin for an organization by adding the specified user to the organization's admin list. + * + * This function performs several checks: + * + * 1. Verifies if the specified organization exists. + * 2. Ensures the current user is found and has an associated app user profile. + * 3. Checks if the current user is the creator of the organization. + * 4. Checks if the specified user exists and is a member of the organization. + * 5. Ensures the specified user is not already an admin of the organization. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `data`: An object containing: + * - `organizationId`: The ID of the organization to which the user will be added as an admin. + * - `userId`: The ID of the user to be made an admin. + * @param context - The context of the entire application, including user information and other context-specific data. + * + * @returns An object containing: + * - `user`: The updated app user profile of the user being added as an admin. + * - `userErrors`: An array of error objects if any errors occurred, otherwise an empty array. + * + * @remarks The function handles the following: + * - Caches and retrieves the organization data. + * - Verifies the existence and profile of the current user. + * - Ensures the user to be added is a member of the organization and is not already an admin. + * - Updates the organization's admin list and the app user profile of the newly added admin. */ export const createAdmin: MutationResolvers["createAdmin"] = async ( _parent, @@ -50,13 +65,8 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( await cacheOrganizations([organization as InterfaceOrganization]); } - // Checks whether organization exists. + // Checks whether the organization exists. if (!organization) { - // throw new errors.NotFoundError( - // requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - // ORGANIZATION_NOT_FOUND_ERROR.CODE, - // ORGANIZATION_NOT_FOUND_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -69,16 +79,13 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( ], }; } - // Checks whether the current user is a superAdmin + + // Checks whether the current user exists and has an app user profile. const currentUser = await User.findById({ _id: context.userId, }); + if (!currentUser) { - // throw new errors.NotFoundError( - // requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - // USER_NOT_FOUND_ERROR.CODE, - // USER_NOT_FOUND_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -89,16 +96,12 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( ], }; } + const currentUserAppProfile = await AppUserProfile.findOne({ userId: currentUser._id, }).lean(); if (!currentUserAppProfile) { - // throw new errors.UnauthorizedError( - // requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - // USER_NOT_AUTHORIZED_ERROR.CODE, - // USER_NOT_AUTHORIZED_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -111,16 +114,12 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( } superAdminCheck(currentUserAppProfile as InterfaceAppUserProfile); + const userAppProfile = await AppUserProfile.findOne({ userId: args.data.userId, }).lean(); - 1; + if (!userAppProfile) { - // throw new errors.NotFoundError( - // requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - // USER_NOT_FOUND_ERROR.CODE, - // USER_NOT_FOUND_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -131,34 +130,13 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( ], }; } - // const userExists = !!(await User.exists({ - // _id: args.data.userId, - // })); - - // // Checks whether user with _id === args.data.userId exists. - // if (userExists === false) { - // return { - // user: new AppUserProfile(), - // userErrors: [ - // { - // __typename: "UserNotFoundError", - // message: requestContext.translate("test"), - // }, - // ], - // }; - // } + // Checks if the user is a member of the organization. const userIsOrganizationMember = organization.members.some((member) => new mongoose.Types.ObjectId(member.toString()).equals(args.data.userId), ); - // Checks whether user with _id === args.data.userId is not a member of organization. if (userIsOrganizationMember === false) { - // throw new errors.NotFoundError( - // requestContext.translate(ORGANIZATION_MEMBER_NOT_FOUND_ERROR.MESSAGE), - // ORGANIZATION_MEMBER_NOT_FOUND_ERROR.CODE, - // ORGANIZATION_MEMBER_NOT_FOUND_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -172,17 +150,12 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( }; } + // Checks if the user is already an admin of the organization. const userIsOrganizationAdmin = organization.admins.some((admin) => new mongoose.Types.ObjectId(admin.toString()).equals(args.data.userId), ); - // Checks whether user with _id === args.data.userId is already an admin of organization. if (userIsOrganizationAdmin === true) { - // throw new errors.UnauthorizedError( - // requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - // USER_NOT_AUTHORIZED_ERROR.CODE, - // USER_NOT_AUTHORIZED_ERROR.PARAM, - // ); return { user: new AppUserProfile(), userErrors: [ @@ -194,7 +167,7 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( }; } - // Adds args.data.userId to admins list of organization's document. + // Updates the organization document to add the user as an admin. const updatedOrganization = await Organization.findOneAndUpdate( { _id: organization._id, @@ -213,10 +186,7 @@ export const createAdmin: MutationResolvers["createAdmin"] = async ( await cacheOrganizations([updatedOrganization]); } - /* - Adds organization._id to adminFor list on appUserProfile's document with userId === args.data.userId - and returns the updated appUserProfile of the user. - */ + // Updates the app user profile to reflect the new admin role. return { user: await AppUserProfile.findOneAndUpdate( { diff --git a/src/resolvers/Mutation/createAdvertisement.ts b/src/resolvers/Mutation/createAdvertisement.ts index c623223149..1b5347851c 100644 --- a/src/resolvers/Mutation/createAdvertisement.ts +++ b/src/resolvers/Mutation/createAdvertisement.ts @@ -11,6 +11,29 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo"; +/** + * Mutation resolver function to create a new advertisement. + * + * This function performs the following actions: + * 1. Verifies that the current user, identified by `context.userId`, exists. + * 2. Ensures that the organization specified by `args.input.organizationId` exists. + * 3. Uploads the media file if provided, determining its type (image or video). + * 4. Creates a new advertisement with the provided details. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `input`: An object containing: + * - `organizationId`: The ID of the organization where the advertisement will be created. + * - `mediaFile`: The encoded media file (image or video) to be uploaded. + * - Other advertisement details. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. + * - `apiRootUrl`: The root URL for the API to construct the media URL. + * + * @returns An object containing the created advertisement, including: + * - `advertisement`: The created advertisement details with the media URL. + * + */ export const createAdvertisement: MutationResolvers["createAdvertisement"] = async (_parent, args, context) => { // Get the current user diff --git a/src/resolvers/Mutation/createAgendaCategory.ts b/src/resolvers/Mutation/createAgendaCategory.ts index 0be0342a74..85c0176d1f 100644 --- a/src/resolvers/Mutation/createAgendaCategory.ts +++ b/src/resolvers/Mutation/createAgendaCategory.ts @@ -19,22 +19,36 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { adminCheck } from "../../utilities"; + /** - * This is a resolver function for the GraphQL mutation 'createAgendaCategory'. + * Creates a new agenda category and associates it with a specified organization. + * + * This resolver function performs the following steps: + * + * 1. Retrieves the current user based on the userId from the context. + * 2. Fetches the associated app user profile for the current user. + * 3. Retrieves the organization specified in the input, either from the cache or from the database. + * 4. Validates the existence of the organization. + * 5. Checks if the current user is authorized to perform this operation. + * 6. Creates a new agenda category and associates it with the specified organization. + * 7. Updates the organization document with the new agenda category. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the request, including: + * - `input`: An object containing: + * - `organizationId`: The ID of the organization to which the new agenda category will be added. + * - `name`: The name of the new agenda category. + * - `description`: A description of the new agenda category. + * @param context - The context of the entire application, including user information (context.userId). * - * This resolver creates a new agenda category, associates it with an organization, - * and updates the organization with the new agenda category. + * @returns A promise that resolves to the created agenda category object. * - * @returns A promise that resolves to the created agenda category. - * @throws `NotFoundError` If the user or organization is not found. - * @throws `UnauthorizedError` If the user does not have the required permissions. - * @throws `InternalServerError` For other potential issues during agenda category creation. + * @remarks The function performs caching and retrieval operations to ensure the latest data is used, + * and it updates the organization document to include the new agenda category. */ - export const createAgendaCategory: MutationResolvers["createAgendaCategory"] = async (_parent, args, context) => { - // Find the current user based on the provided createdBy ID or use the context userId - + // Find the current user based on the provided userId from the context const userId = context.userId; let currentUser: InterfaceUser | null; @@ -91,7 +105,7 @@ export const createAgendaCategory: MutationResolvers["createAgendaCategory"] = await cacheOrganizations([organization]); } - // Checks whether the organization with _id === args.organizationId exists. + // Checks whether the organization with _id === args.input.organizationId exists. if (!organization) { throw new errors.NotFoundError( requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), @@ -109,6 +123,8 @@ export const createAgendaCategory: MutationResolvers["createAgendaCategory"] = createdBy: currentUser?._id, createdAt: new Date(), }); + + // Update the organization's document to include the new agenda category await Organization.findByIdAndUpdate( organization._id, { diff --git a/src/resolvers/Mutation/createAgendaItem.ts b/src/resolvers/Mutation/createAgendaItem.ts index 342a4293ee..6d75d6a17b 100644 --- a/src/resolvers/Mutation/createAgendaItem.ts +++ b/src/resolvers/Mutation/createAgendaItem.ts @@ -27,13 +27,28 @@ import { findOrganizationsInCache } from "../../services/OrganizationCache/findO import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * Create an agenda item based on the provided input. + * Creates a new agenda item and associates it with an event if specified. + * + * This function performs the following actions: + * 1. Verifies that the current user exists and is authorized. + * 2. Checks the existence of the specified organization. + * 3. If a related event is specified, verifies its existence and checks if the user is an admin of the event. + * 4. Checks if the user is an admin of the organization or has super admin privileges. + * 5. Creates the new agenda item and associates it with the event if applicable. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `input`: An object containing: + * - `organizationId`: The ID of the organization where the agenda item will be created. + * - `relatedEventId` (optional): The ID of the related event, if applicable. + * - Other agenda item details. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user making the request. * - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application * @returns The created agenda item. + * */ export const createAgendaItem: MutationResolvers["createAgendaItem"] = async ( _parent, diff --git a/src/resolvers/Mutation/createAgendaSection.ts b/src/resolvers/Mutation/createAgendaSection.ts index a54d32d50d..71d90ac865 100644 --- a/src/resolvers/Mutation/createAgendaSection.ts +++ b/src/resolvers/Mutation/createAgendaSection.ts @@ -13,14 +13,26 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * Resolver function for the GraphQL mutation 'createAgendaSection'. + * Creates a new agenda section and performs authorization checks. * - * This resolver creates a new agenda section and performs necessary authorization checks. + * This resolver performs the following steps: * - * @param _parent - The parent object, not used in this resolver. - * @param args - The input arguments for the mutation. - * @param context - The context object containing user information. - * @returns A promise that resolves to the created agenda section. + * 1. Retrieves the current user based on the userId from the context. + * 2. Fetches the associated app user profile for the current user. + * 3. Validates the existence of the related event and checks user permissions. + * 4. Creates a new agenda section and sets the appropriate metadata. + * + * @param _parent - The parent object for the mutation (not used in this function). + * @param args - The arguments provided with the mutation, including: + * - `input`: An object containing: + * - `relatedEvent`: The ID of the event to which the new agenda section is related. + * - Additional fields for the agenda section. + * @param context - The context of the entire application, including user information (context.userId). + * + * @returns A promise that resolves to the created agenda section object. + * + * @remarks This function performs caching and retrieval operations to ensure the latest data is used. + * It also verifies that the user has the necessary permissions to create the agenda section in the context of the specified event. */ export const createAgendaSection: MutationResolvers["createAgendaSection"] = async (_parent, args, context) => { diff --git a/src/resolvers/Mutation/createComment.ts b/src/resolvers/Mutation/createComment.ts index 44521a12c8..8e1f83a7dd 100644 --- a/src/resolvers/Mutation/createComment.ts +++ b/src/resolvers/Mutation/createComment.ts @@ -6,13 +6,23 @@ import { cacheComments } from "../../services/CommentCache/cacheComments"; import { cachePosts } from "../../services/PostCache/cachePosts"; /** - * This function enables to create comment. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * @returns Created comment + * Creates a new comment and associates it with the specified post. + * + * This function performs the following actions: + * 1. Verifies that the post specified by `postId` exists. + * 2. Creates a new comment associated with the post. + * 3. Increments the `commentCount` for the post by 1. + * 4. Caches the newly created comment and updated post data. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `postId`: The ID of the post to which the comment will be associated. + * - `data`: The comment data, including the content of the comment. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user creating the comment. + * + * @returns The created comment. + * */ export const createComment: MutationResolvers["createComment"] = async ( _parent, diff --git a/src/resolvers/Mutation/createDirectChat.ts b/src/resolvers/Mutation/createDirectChat.ts index 6c8675790f..85d352638a 100644 --- a/src/resolvers/Mutation/createDirectChat.ts +++ b/src/resolvers/Mutation/createDirectChat.ts @@ -1,51 +1,35 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { User, Organization, DirectChat } from "../../models"; +import { User, DirectChat } from "../../models"; import { errors, requestContext } from "../../libraries"; -import { - USER_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, -} from "../../constants"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; + +import { USER_NOT_FOUND_ERROR } from "../../constants"; + /** - * This function enables to create direct chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the organization exists - * 2. If the user exists - * @returns Created chat + * Creates a new direct chat and associates it with an organization. + * + * This resolver performs the following steps: + * + * 1. Retrieves the organization based on the provided `organizationId`. + * 2. Checks if the organization exists, either from cache or database. + * 3. Validates that all user IDs provided in `userIds` exist. + * 4. Creates a new direct chat with the specified users and organization. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `organizationId`: The ID of the organization to associate with the direct chat. + * - `userIds`: An array of user IDs to be included in the direct chat. + * @param context - The context object containing user information (context.userId). + * + * @returns A promise that resolves to the created direct chat object. + * + * @remarks This function includes caching operations to optimize data retrieval and ensures that all user IDs are valid before creating the direct chat. */ export const createDirectChat: MutationResolvers["createDirectChat"] = async ( _parent, args, context, ) => { - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - args.data.organizationId, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: args.data.organizationId, - }).lean(); - if (organization) await cacheOrganizations([organization]); - } - - // Checks whether organization with _id === args.data.organizationId exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - // Variable to store list of users to be members of directChat. const usersInDirectChat = []; @@ -71,7 +55,6 @@ export const createDirectChat: MutationResolvers["createDirectChat"] = async ( const createdDirectChat = await DirectChat.create({ creatorId: context.userId, users: usersInDirectChat, - organization: args.data.organizationId, }); // Returns createdDirectChat. diff --git a/src/resolvers/Mutation/createDonation.ts b/src/resolvers/Mutation/createDonation.ts index d082f85bb1..794a0a336f 100644 --- a/src/resolvers/Mutation/createDonation.ts +++ b/src/resolvers/Mutation/createDonation.ts @@ -2,11 +2,23 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { Donation } from "../../models"; /** - * This function enables to create a donation as transaction - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @returns Created Donation + * Creates a new donation transaction. + * + * This function performs the following actions: + * 1. Creates a new donation record in the database with the specified details. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `amount`: The amount of the donation. + * - `nameOfOrg`: The name of the organization receiving the donation. + * - `nameOfUser`: The name of the user making the donation. + * - `orgId`: The ID of the organization receiving the donation. + * - `payPalId`: The PayPal ID associated with the transaction. + * - `userId`: The ID of the user making the donation. + * @param context - The context for the mutation, which is not used in this resolver. + * + * @returns The created donation record. + * */ export const createDonation: MutationResolvers["createDonation"] = async ( _parent, diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index 8898c0d805..ac7acc931a 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -27,20 +27,32 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables to create an event. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2.If the user has appUserProfile - * 3. If the organization exists - * 4. If the user is a part of the organization. - * 5. If the event is recurring, create the recurring event instances. - * 6. If the event is non-recurring, create a single event. - * @returns Created event + * Creates a new event and associates it with an organization. + * + * This resolver handles both recurring and non-recurring events, performing the following steps: + * + * 1. Validates the existence of the user, their app user profile, and the associated organization. + * 2. Checks if the user is authorized to create an event in the organization. + * 3. Validates the provided event details, including title, description, location, and date range. + * 4. Creates the event using the appropriate method based on whether it's recurring or not. + * 5. Uses a database transaction to ensure data consistency. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `organizationId`: The ID of the organization to associate with the event. + * - `title`: The title of the event (max 256 characters). + * - `description`: A description of the event (max 500 characters). + * - `location`: The location of the event (max 50 characters). + * - `startDate`: The start date of the event. + * - `endDate`: The end date of the event. + * - `recurring`: A boolean indicating if the event is recurring. + * @param context - The context object containing user information (context.userId). + * + * @returns A promise that resolves to the created event object. + * + * @remarks This function uses a transaction to ensure that either all operations succeed or none do, maintaining data integrity. */ - export const createEvent: MutationResolvers["createEvent"] = async ( _parent, args, diff --git a/src/resolvers/Mutation/createEventVolunteer.ts b/src/resolvers/Mutation/createEventVolunteer.ts index f5782350a6..decaa931fc 100644 --- a/src/resolvers/Mutation/createEventVolunteer.ts +++ b/src/resolvers/Mutation/createEventVolunteer.ts @@ -14,19 +14,28 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables to create an event volunteer. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2. if the volunteer user exists - * 3. If the event exists - * 4. If the group exists - * 5. If the current user is leader of the group - * @returns Created event volunteer + * Creates a new event volunteer entry. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user. + * 2. Verifies the existence of the volunteer user. + * 3. Verifies the existence of the event. + * 4. Verifies the existence of the volunteer group. + * 5. Ensures that the current user is the leader of the volunteer group. + * 6. Creates a new event volunteer record. + * 7. Adds the newly created volunteer to the group's list of volunteers. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.userId`: The ID of the user to be assigned as a volunteer. + * - `data.eventId`: The ID of the event for which the volunteer is being created. + * - `data.groupId`: The ID of the volunteer group to which the user is being added. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user performing the operation. + * + * @returns The created event volunteer record. + * */ - export const createEventVolunteer: MutationResolvers["createEventVolunteer"] = async (_parent, args, context) => { let currentUser: InterfaceUser | null; diff --git a/src/resolvers/Mutation/createEventVolunteerGroup.ts b/src/resolvers/Mutation/createEventVolunteerGroup.ts index 0c2060d810..060a2dde49 100644 --- a/src/resolvers/Mutation/createEventVolunteerGroup.ts +++ b/src/resolvers/Mutation/createEventVolunteerGroup.ts @@ -11,17 +11,28 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This function enables to create an event volunteer group - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2. If the eventId exists - * 3. If the current user is admin of event - * @returns Created event volunteer group + * Creates a new event volunteer group and associates it with an event. + * + * This resolver performs the following actions: + * + * 1. Validates the existence of the current user. + * 2. Checks if the specified event exists. + * 3. Verifies that the current user is an admin of the event. + * 4. Creates a new volunteer group for the event. + * 5. Updates the event to include the newly created volunteer group. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `eventId`: The ID of the event to associate the volunteer group with. + * - `name`: The name of the volunteer group. + * - `volunteersRequired`: The number of volunteers required for the group. + * @param context - The context object containing user information (context.userId). + * + * @returns A promise that resolves to the created event volunteer group object. + * + * @remarks This function first checks the cache for the current user and then queries the database if needed. It ensures that the user is authorized to create a volunteer group for the event before proceeding. */ - export const createEventVolunteerGroup: MutationResolvers["createEventVolunteerGroup"] = async (_parent, args, context) => { let currentUser: InterfaceUser | null; diff --git a/src/resolvers/Mutation/createFund.ts b/src/resolvers/Mutation/createFund.ts index a987fa8e76..bb595b2910 100644 --- a/src/resolvers/Mutation/createFund.ts +++ b/src/resolvers/Mutation/createFund.ts @@ -14,19 +14,32 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { adminCheck } from "../../utilities"; + /** - * This function enables to create an organization specific fundraising funds. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists - * 3. If the user is authorized. - * 4. If the fund already exists - * @returns Created fund + * Creates a new fundraising fund for a specified organization. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user. + * 2. Retrieves and caches the user's profile if not already cached. + * 3. Verifies the existence of the specified organization. + * 4. Checks if the current user is an admin of the organization. + * 5. Verifies that the fund does not already exist for the given organization. + * 6. Creates a new fund with the provided details. + * 7. Updates the organization's list of funds to include the newly created fund. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.organizationId`: The ID of the organization for which the fund is being created. + * - `data.name`: The name of the fund. + * - `data.refrenceNumber`: The reference number for the fund. + * - `data.taxDeductible`: Indicates if the fund is tax-deductible. + * - `data.isDefault`: Indicates if the fund is a default fund. + * - `data.isArchived`: Indicates if the fund is archived. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user performing the operation. + * + * @returns The created fund record. */ - export const createFund: MutationResolvers["createFund"] = async ( _parent, args, @@ -86,22 +99,25 @@ export const createFund: MutationResolvers["createFund"] = async ( ORGANIZATION_NOT_FOUND_ERROR.PARAM, ); } - //checks whether the user is admin of organization or not + + // Checks whether the user is admin of the organization or not. await adminCheck(currentUser._id, organization); - const exisitingFund = await Fund.findOne({ + const existingFund = await Fund.findOne({ name: args.data.name, organizationId: args.data.organizationId, }); - //checks if the fund already exists - if (exisitingFund) { + + // Checks if the fund already exists. + if (existingFund) { throw new errors.ConflictError( requestContext.translate(FUND_ALREADY_EXISTS.MESSAGE), FUND_ALREADY_EXISTS.CODE, FUND_ALREADY_EXISTS.PARAM, ); } - //create Fund with the provided data + + // Creates Fund with the provided data. const createdFund = await Fund.create({ name: args.data.name, organizationId: args.data.organizationId, @@ -112,7 +128,7 @@ export const createFund: MutationResolvers["createFund"] = async ( creatorId: context.userId, }); - //push the created fund to the organization funds array + // Pushes the created fund to the organization's funds array. await Organization.updateOne( { _id: organization._id, diff --git a/src/resolvers/Mutation/createFundraisingCampaign.ts b/src/resolvers/Mutation/createFundraisingCampaign.ts index c729e71d57..b055d69b9e 100644 --- a/src/resolvers/Mutation/createFundraisingCampaign.ts +++ b/src/resolvers/Mutation/createFundraisingCampaign.ts @@ -14,20 +14,34 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { validateDate } from "../../utilities/dateValidator"; + /** - * This function enables to create a fundraisingCampaigin . - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2 .If the startDate is valid - * 3. If the endDate is valid - * 4. if the parent fund exists - * 5. If the user is authorized. - * @returns Created fundraisingCampaign + * Creates a new fundraising campaign and associates it with a specified fund. + * + * This resolver performs the following actions: + * + * 1. Validates the existence of the current user. + * 2. Checks if the user has an associated profile and if they are authorized. + * 3. Ensures that a fundraising campaign with the same name does not already exist. + * 4. Validates the provided start and end dates for the campaign. + * 5. Verifies the existence of the specified fund and checks if the user is authorized to create a campaign for the fund. + * 6. Creates a new fundraising campaign and associates it with the fund. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `name`: The name of the fundraising campaign. + * - `fundId`: The ID of the fund to associate the campaign with. + * - `startDate`: The start date of the campaign. + * - `endDate`: The end date of the campaign. + * - `fundingGoal`: The funding goal for the campaign. + * - `currency`: The currency for the funding goal. + * @param context - The context object containing user information (context.userId). + * + * @returns A promise that resolves to the created fundraising campaign object. + * + * @remarks This function checks the cache for user and profile data, validates inputs, and ensures the user has the necessary permissions before creating the campaign. */ - export const createFundraisingCampaign: MutationResolvers["createFundraisingCampaign"] = async (_parent, args, context): Promise => { let currentUser: InterfaceUser | null; @@ -82,10 +96,11 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp FUNDRAISING_CAMPAIGN_ALREADY_EXISTS.PARAM, ); } + const startDate = args.data.startDate; const endDate = args.data.endDate; - //validates StartDate and endDate + // Validates startDate and endDate validateDate(startDate, endDate); const fund = await Fund.findOne({ @@ -99,6 +114,7 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp FUND_NOT_FOUND_ERROR.PARAM, ); } + const currentOrg = await Fund.findById(fund._id) .select("organizationId") .lean(); @@ -121,7 +137,7 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp ); } console.log("here"); - // Creates a fundraisingCampaign. + // Creates a fundraising campaign. const campaign = await FundraisingCampaign.create({ name: args.data.name, fundId: args.data.fundId, @@ -131,7 +147,7 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp currency: args.data.currency, }); - //add campaigin to the parent fund + // Adds campaign to the parent fund await Fund.findOneAndUpdate( { _id: args.data.fundId, diff --git a/src/resolvers/Mutation/createFundraisingCampaignPledge.ts b/src/resolvers/Mutation/createFundraisingCampaignPledge.ts index 72b5a158c4..4a71863a4b 100644 --- a/src/resolvers/Mutation/createFundraisingCampaignPledge.ts +++ b/src/resolvers/Mutation/createFundraisingCampaignPledge.ts @@ -13,17 +13,31 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import { type MutationResolvers } from "../../types/generatedGraphQLTypes"; import { validateDate } from "../../utilities/dateValidator"; + /** - * This function enables to create a fundraisingCampaiginPledge . - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the current user exists - * 2 .If the startDate is valid - * 3. If the endDate is valid - * 4. if the fund campaign exists - * @returns Created fundraisingCampaignPledge + * Creates a new pledge for a fundraising campaign. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user. + * 2. Retrieves and caches the user's details if not already cached. + * 3. Checks the validity of the provided or default campaign start and end dates. + * 4. Verifies the existence of the specified fundraising campaign. + * 5. Creates a new pledge for the specified campaign with the given details. + * 6. Updates the campaign to include the newly created pledge. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.campaignId`: The ID of the fundraising campaign for which the pledge is being created. + * - `data.userIds`: An array of user IDs associated with the pledge. + * - `data.startDate`: The start date of the pledge (optional; defaults to the campaign's start date). + * - `data.endDate`: The end date of the pledge (optional; defaults to the campaign's end date). + * - `data.amount`: The amount pledged. + * - `data.currency`: The currency of the pledged amount. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user performing the operation. + * + * @returns The created pledge record. + * */ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisingCampaignPledge"] = async ( @@ -51,6 +65,7 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi USER_NOT_FOUND_ERROR.PARAM, ); } + const campaign = await FundraisingCampaign.findOne({ _id: args.data.campaignId, }).lean(); @@ -64,14 +79,14 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi ); } - //if startDate and endDate are not provided, then use the campaign's startDate and endDate + // If startDate and endDate are not provided, then use the campaign's startDate and endDate. const startDate = args.data?.startDate ?? campaign.startDate; const endDate = args.data?.endDate ?? campaign.endDate; - //validates startDate and endDate + // Validates startDate and endDate. validateDate(startDate, endDate); - // Create a new pledge + // Create a new pledge. const pledge = await FundraisingCampaignPledge.create({ campaigns: [args.data.campaignId], users: args.data.userIds, @@ -81,7 +96,7 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi currency: args.data.currency, }); - // Update the campaign with the new pledge + // Update the campaign with the new pledge. await FundraisingCampaign.updateOne( { _id: args.data.campaignId, @@ -90,5 +105,6 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi $push: { pledges: pledge._id }, }, ); + return pledge as InterfaceFundraisingCampaignPledges; }; diff --git a/src/resolvers/Mutation/createGroupChat.ts b/src/resolvers/Mutation/createGroupChat.ts index 2609cec40f..17dc1548b0 100644 --- a/src/resolvers/Mutation/createGroupChat.ts +++ b/src/resolvers/Mutation/createGroupChat.ts @@ -7,15 +7,28 @@ import { GroupChat, Organization, User } from "../../models"; import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This function enables to create a group chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists - * @returns Created group chat + * Creates a new group chat and associates it with a specified organization. + * + * This resolver performs the following actions: + * + * 1. Checks if the specified organization exists in the cache, and if not, fetches it from the database and caches it. + * 2. Verifies that the organization with the given ID exists. + * 3. Checks if each user specified in the `userIds` list exists. + * 4. Creates a new group chat with the specified users, organization, and title. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `organizationId`: The ID of the organization to associate with the group chat. + * - `userIds`: A list of user IDs to be added to the group chat. + * - `title`: The title of the group chat. + * @param context - The context object containing user information (context.userId). + * + * @returns A promise that resolves to the created group chat object. + * + * @remarks This function ensures the existence of the organization and users, and caches the organization if it is not already cached. It returns the created group chat object. */ export const createGroupChat: MutationResolvers["createGroupChat"] = async ( _parent, @@ -37,7 +50,7 @@ export const createGroupChat: MutationResolvers["createGroupChat"] = async ( if (organization) await cacheOrganizations([organization]); } - // Checks whether organization with _id === args.data.organizationId exists. + // Checks whether the organization with _id === args.data.organizationId exists. if (!organization) { throw new errors.NotFoundError( requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), @@ -46,7 +59,7 @@ export const createGroupChat: MutationResolvers["createGroupChat"] = async ( ); } - // Variable to store list of users to be members of groupChat. + // Variable to store list of users to be members of group chat. const usersInGroupChat = []; // Loops over each item in args.data.userIds list. @@ -67,7 +80,7 @@ export const createGroupChat: MutationResolvers["createGroupChat"] = async ( usersInGroupChat.push(userId); } - // Creates new groupChat. + // Creates new group chat. const createdGroupChat = await GroupChat.create({ creatorId: context.userId, users: usersInGroupChat, @@ -75,6 +88,6 @@ export const createGroupChat: MutationResolvers["createGroupChat"] = async ( title: args.data?.title, }); - // Returns createdGroupChat. + // Returns created group chat. return createdGroupChat.toObject(); }; diff --git a/src/resolvers/Mutation/createMember.ts b/src/resolvers/Mutation/createMember.ts index c492faf56b..7727844edf 100644 --- a/src/resolvers/Mutation/createMember.ts +++ b/src/resolvers/Mutation/createMember.ts @@ -20,19 +20,29 @@ import { findOrganizationsInCache } from "../../services/OrganizationCache/findO import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This function enables to add a member. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. Checks whether current user making the request is an superAdmin or an Admin. - * 2. If the organization exists - * 3. Checks whether curent user exists. - * 4. Checks whether current user has appProfile. - * 4. Checks whether user with _id === args.input.userId is already an member of organization.. + * Adds a user as a member to an organization. + * + * This resolver performs the following actions: + * + * 1. Verifies if the current user making the request exists and is either a superAdmin or an admin of the organization. + * 2. Checks if the specified organization exists in the cache; if not, fetches it from the database and caches it. + * 3. Checks if the specified user exists and is not already a member of the organization. + * 4. Adds the user to the organization's member list and updates the user's joinedOrganizations list. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `input`: An object containing: + * - `organizationId`: The ID of the organization to which the user will be added. + * - `userId`: The ID of the user to be added as a member. + * @param context - The context object containing user information (context.userId). * - * @returns Organization. + * @returns An object containing: + * - `organization`: The updated organization object. + * - `userErrors`: A list of errors encountered during the process. + * + * @remarks This function returns the updated organization and any errors encountered. It ensures that the user is not already a member before adding them and handles caching of the organization. */ export const createMember: MutationResolvers["createMember"] = async ( _parent, @@ -53,11 +63,7 @@ export const createMember: MutationResolvers["createMember"] = async ( } if (!currentUser) { - // throw new errors.NotFoundError( - // requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - // USER_NOT_FOUND_ERROR.CODE, - // USER_NOT_FOUND_ERROR.PARAM, - // ); + // Return an error if the user is not found return { organization: new Organization(), userErrors: [ @@ -68,6 +74,7 @@ export const createMember: MutationResolvers["createMember"] = async ( ], }; } + let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), @@ -83,11 +90,7 @@ export const createMember: MutationResolvers["createMember"] = async ( } if (!currentUserAppProfile) { - // throw new errors.UnauthorizedError( - // requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - // USER_NOT_AUTHORIZED_ERROR.CODE, - // USER_NOT_AUTHORIZED_ERROR.PARAM, - // ); + // Return an error if the user's app profile is not found return { organization: new Organization(), userErrors: [ @@ -117,11 +120,7 @@ export const createMember: MutationResolvers["createMember"] = async ( } if (!organization) { - // throw new errors.NotFoundError( - // requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - // ORGANIZATION_NOT_FOUND_ERROR.CODE, - // ORGANIZATION_NOT_FOUND_ERROR.PARAM, - // ); + // Return an error if the organization is not found return { organization: new Organization(), userErrors: [ @@ -134,12 +133,14 @@ export const createMember: MutationResolvers["createMember"] = async ( ], }; } + const userIsOrganizationAdmin = organization.admins.some( (admin) => admin === currentUser._id || new mongoose.Types.ObjectId(admin.toString()).equals(currentUser._id), ); if (!userIsOrganizationAdmin && !currentUserAppProfile.isSuperAdmin) { + // Return an error if the user is not authorized return { organization: new Organization(), userErrors: [ @@ -155,13 +156,9 @@ export const createMember: MutationResolvers["createMember"] = async ( _id: args.input.userId, }).lean(); - // Checks whether curent user exists + // Checks whether the user exists if (!user) { - // throw new errors.NotFoundError( - // requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - // USER_NOT_FOUND_ERROR.CODE, - // USER_NOT_FOUND_ERROR.PARAM, - // ); + // Return an error if the user is not found return { organization: new Organization(), userErrors: [ @@ -177,13 +174,9 @@ export const createMember: MutationResolvers["createMember"] = async ( member.equals(user._id), ); - // Checks whether user with _id === args.input.userId is already an member of organization. + // Checks whether the user is already a member of the organization if (userIsOrganizationMember) { - // throw new errors.NotFoundError( - // requestContext.translate(MEMBER_NOT_FOUND_ERROR.MESSAGE), - // MEMBER_NOT_FOUND_ERROR.CODE, - // MEMBER_NOT_FOUND_ERROR.PARAM, - // ); + // Return an error if the user is already a member return { organization: new Organization(), userErrors: [ @@ -195,7 +188,7 @@ export const createMember: MutationResolvers["createMember"] = async ( }; } - // add organization's id from joinedOrganizations list on user. + // Adds the organization ID to the user's joinedOrganizations list. await User.updateOne( { _id: args.input.userId, @@ -210,7 +203,7 @@ export const createMember: MutationResolvers["createMember"] = async ( }, ); - // add user's id to members list on organization and return it. + // Adds the user's ID to the organization's members list and returns it. const updatedOrganization = await Organization.findOneAndUpdate( { _id: organization?._id, diff --git a/src/resolvers/Mutation/createMessageChat.ts b/src/resolvers/Mutation/createMessageChat.ts index 60a9975499..e7d6fdab4c 100644 --- a/src/resolvers/Mutation/createMessageChat.ts +++ b/src/resolvers/Mutation/createMessageChat.ts @@ -10,16 +10,29 @@ import { findAppUserProfileCache } from "../../services/AppUserProfileCache/find import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This function enables to create a chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the receiver user exists - * 2. If the sender and receiver users have same language code. - * 3. If the sender and receiver users have appProfile. - * @returns Created message chat. + * Creates a new chat message between users. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user. + * 2. Retrieves and caches the current user's details and application profile if not already cached. + * 3. Checks the existence of the receiver user and retrieves their application profile. + * 4. Ensures that both the current user and the receiver have valid application profiles. + * 5. Compares the language codes of the sender and receiver to determine if there is a language barrier. + * 6. Creates a new chat message with the specified content and language barrier status. + * 7. Publishes the created message chat to a pub/sub channel for real-time updates. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.receiver`: The ID of the user receiving the message. + * - `data.message`: The content of the message being sent. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user sending the message. + * - `pubsub`: The pub/sub instance for publishing real-time updates. + * + * @returns The created message chat record. + * */ export const createMessageChat: MutationResolvers["createMessageChat"] = async ( _parent, @@ -89,6 +102,7 @@ export const createMessageChat: MutationResolvers["createMessageChat"] = async ( USER_NOT_AUTHORIZED_ERROR.PARAM, ); } + // Boolean to identify whether both sender and receiver for messageChat have the same appLanguageCode. const isSenderReceiverLanguageSame = receiverUserAppProfile?.appLanguageCode === diff --git a/src/resolvers/Mutation/createNote.ts b/src/resolvers/Mutation/createNote.ts index f1a456fe52..f510d8b40a 100644 --- a/src/resolvers/Mutation/createNote.ts +++ b/src/resolvers/Mutation/createNote.ts @@ -14,13 +14,28 @@ import { findAppUserProfileCache } from "../../services/AppUserProfileCache/find import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * Create an note for an agenda item based on the provided input. + * Creates a note for a specified agenda item. + * + * This resolver performs the following actions: + * + * 1. Verifies the existence of the current user making the request. + * 2. Checks the user's app profile to ensure they are authenticated. + * 3. Checks if the specified agenda item exists. + * 4. Creates a new note associated with the agenda item. + * 5. Updates the agenda item to include the newly created note. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `agendaItemId`: The ID of the agenda item to which the note will be added. + * - `content`: The content of the note. + * @param context - The context object containing user information (context.userId). + * + * @returns The created note object. * - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @returns The created note for an agenda item. + * @remarks This function creates a note, associates it with the specified agenda item, and updates the agenda item to include the new note. It also handles caching and error scenarios. */ export const createNote: MutationResolvers["createNote"] = async ( _parent, diff --git a/src/resolvers/Mutation/createOrganization.ts b/src/resolvers/Mutation/createOrganization.ts index b68fe9e89b..afb7bb0297 100644 --- a/src/resolvers/Mutation/createOrganization.ts +++ b/src/resolvers/Mutation/createOrganization.ts @@ -24,15 +24,33 @@ import { superAdminCheck } from "../../utilities"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; + /** - * This function enables to create an organization. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the user has appUserProfile - * @returns Created organization + * Creates a new organization. + * + * This resolver performs the following steps: + * + * 1. Verifies the existence of the current user making the request. + * 2. Checks the user's app profile to ensure they are authenticated and authorized as a super admin. + * 3. Validates the provided input data, including organization name, description, and address. + * 4. Uploads an optional image file associated with the organization. + * 5. Creates a new organization with the provided data and image. + * 6. Creates a default action item category for the new organization. + * 7. Updates the current user's document to include the new organization in their `joinedOrganizations`, `createdOrganizations`, and `adminFor` lists. + * 8. Caches the newly created organization. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including: + * - `data`: An object containing: + * - `name`: The name of the organization. + * - `description`: A description of the organization. + * - `address`: An optional address object for the organization. + * - `file`: An optional encoded image file for the organization. + * @param context - The context object containing user information (context.userId). + * + * @returns The created organization object. + * + * @remarks This function creates an organization, uploads an optional image, validates the input data, creates a default action item category, updates user records, and manages caching. */ export const createOrganization: MutationResolvers["createOrganization"] = async (_parent, args, context) => { @@ -77,13 +95,13 @@ export const createOrganization: MutationResolvers["createOrganization"] = } superAdminCheck(currentUserAppProfile as InterfaceAppUserProfile); - //Upload file + // Upload file let uploadImageFileName = null; if (args.file) { uploadImageFileName = await uploadEncodedImage(args.file, null); } - // Checks if the recieved arguments are valid according to standard input norms + // Validate input arguments let validationResultName = { isLessThanMaxLength: false, }; @@ -123,7 +141,7 @@ export const createOrganization: MutationResolvers["createOrganization"] = throw new errors.InputValidationError("Not a Valid Address"); } - // Creates new organization. + // Create new organization const createdOrganization = await Organization.create({ ...args.data, address: args.data?.address, @@ -133,7 +151,7 @@ export const createOrganization: MutationResolvers["createOrganization"] = members: [context.userId], }); - // Creating a default actionItemCategory + // Create default action item category await ActionItemCategory.create({ name: "Default", organizationId: createdOrganization._id, @@ -142,11 +160,7 @@ export const createOrganization: MutationResolvers["createOrganization"] = await cacheOrganizations([createdOrganization.toObject()]); - /* - Adds createdOrganization._id to joinedOrganizations, createdOrganizations - and adminFor lists on currentUser's document with _id === context.userId - */ - + // Update currentUser's document await User.updateOne( { _id: context.userId, @@ -169,9 +183,10 @@ export const createOrganization: MutationResolvers["createOrganization"] = }, ); - // Returns createdOrganization. + // Return created organization return createdOrganization.toObject(); }; + /** * Validates an address object to ensure its fields meet specified criteria. * @param address - The address object to validate diff --git a/src/resolvers/Mutation/createPlugin.ts b/src/resolvers/Mutation/createPlugin.ts index 290d4db17c..fe45f45b40 100644 --- a/src/resolvers/Mutation/createPlugin.ts +++ b/src/resolvers/Mutation/createPlugin.ts @@ -2,13 +2,22 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { Plugin } from "../../models"; /** - * This function enables to create a plugin. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param _context - context of entire application - * @returns Created plugin + * Creates a new plugin and triggers a subscription event. + * + * This resolver performs the following steps: + * + * 1. Creates a new plugin using the provided arguments. + * 2. Publishes an update event to the `TALAWA_PLUGIN_UPDATED` subscription channel with the created plugin details. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, which include: + * - `data`: An object containing the plugin's details. + * @param _context - The context object, which includes the pubsub system for triggering subscriptions. + * + * @returns The created plugin object. + * + * @remarks This function creates a plugin record, updates the subscription channel with the new plugin details, and returns the created plugin. */ - export const createPlugin: MutationResolvers["createPlugin"] = async ( _parent, args, @@ -19,7 +28,8 @@ export const createPlugin: MutationResolvers["createPlugin"] = async ( ...args, uninstalledOrgs: [], }); - // calls subscription + + // Calls subscription context.pubsub.publish("TALAWA_PLUGIN_UPDATED", { onPluginUpdate: createdPlugin.toObject(), }); diff --git a/src/resolvers/Mutation/createPost.ts b/src/resolvers/Mutation/createPost.ts index 21e7ef611f..659c3497b2 100644 --- a/src/resolvers/Mutation/createPost.ts +++ b/src/resolvers/Mutation/createPost.ts @@ -27,16 +27,39 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo"; + /** - * This function enables to create a post. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists - * 3. If the user has appUserProfile - * @returns Created Post + * Creates a new post and associates it with an organization. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user and retrieves their details and application profile. + * 2. Checks if the specified organization exists and retrieves its details. + * 3. Validates that the user is a member of the organization or is a super admin. + * 4. Handles file uploads for images and videos, if provided. + * 5. Validates the post title and ensures it meets the criteria for pinning. + * 6. Checks user permissions to pin the post if required. + * 7. Creates the post and updates the organization with the pinned post if applicable. + * 8. Caches the newly created post and organization. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.organizationId`: The ID of the organization where the post will be created. + * - `data.title`: The title of the post (optional but required if the post is pinned). + * - `data.text`: The text content of the post. + * - `data.pinned`: A boolean indicating whether the post should be pinned. + * - `file`: An optional base64-encoded image or video file. + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user creating the post. + * - `apiRootUrl`: The root URL of the API for constructing file URLs. + * + * @returns The created post object, including URLs for uploaded image and video files if provided. + * + * @see User - The User model used to interact with user data in the database. + * @see AppUserProfile - The AppUserProfile model used to interact with user profile data in the database. + * @see Organization - The Organization model used to interact with organization data in the database. + * @see Post - The Post model used to interact with post data in the database. + * @see uploadEncodedImage - A utility function for uploading encoded image files. + * @see uploadEncodedVideo - A utility function for uploading encoded video files. */ export const createPost: MutationResolvers["createPost"] = async ( _parent, @@ -152,7 +175,7 @@ export const createPost: MutationResolvers["createPost"] = async ( ); } - // Checks if the recieved arguments are valid according to standard input norms + // Checks if the received arguments are valid according to standard input norms if (args.data?.title && args.data?.text) { const validationResultTitle = isValidString(args.data?.title, 256); const validationResultText = isValidString(args.data?.text, 500); diff --git a/src/resolvers/Mutation/createSampleOrganization.ts b/src/resolvers/Mutation/createSampleOrganization.ts index 255c054c01..895de80ae4 100644 --- a/src/resolvers/Mutation/createSampleOrganization.ts +++ b/src/resolvers/Mutation/createSampleOrganization.ts @@ -14,7 +14,21 @@ import { createSampleOrganization as createSampleOrgUtil } from "../../utilities /** * Generates sample data for testing or development purposes. - * @returns True if the sample data generation is successful, false otherwise. + * + * This resolver performs the following steps: + * + * 1. Verifies that the current user exists and is fetched from the cache or database. + * 2. Checks if the current user has a valid application profile and whether they are authorized. + * 3. Ensures that the current user is a super admin. + * 4. Utilizes a utility function to create a sample organization. + * + * @param _parent - The parent object, not used in this resolver. + * @param _args - The arguments for the mutation, not used in this resolver. + * @param _context - The context object, including the user ID and other necessary context for authorization. + * + * @returns True if the sample data generation is successful; false otherwise. + * + * @remarks This function is intended for creating sample data and should only be accessible by super admins. */ export const createSampleOrganization: MutationResolvers["createSampleOrganization"] = async (_parent, _args, _context) => { diff --git a/src/resolvers/Mutation/createUserFamily.ts b/src/resolvers/Mutation/createUserFamily.ts index 84ad699a13..5a0401a3f6 100644 --- a/src/resolvers/Mutation/createUserFamily.ts +++ b/src/resolvers/Mutation/createUserFamily.ts @@ -17,16 +17,30 @@ import { findAppUserProfileCache } from "../../services/AppUserProfileCache/find import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import { superAdminCheck } from "../../utilities"; + /** - * This Function enables to create a user Family - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks - The following checks are done: - * 1. If the user exists - * 2. If the user is super admin - * 3. If there are atleast two members in the family. - * @returns Created user Family + * Creates a new user family and associates users with it. + * + * This function performs the following actions: + * 1. Verifies the existence of the current user and retrieves their details and application profile. + * 2. Checks if the current user is a super admin. + * 3. Validates the user family name to ensure it does not exceed 256 characters. + * 4. Ensures that the user family has at least two members. + * 5. Creates the user family and associates it with the provided users. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.title`: The title of the user family (must be a string with a maximum length of 256 characters). + * - `data.userIds`: An array of user IDs to be included in the user family (must contain at least 2 members). + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user creating the user family. + * + * @returns The created user family object. + * + * @see User - The User model used to interact with user data in the database. + * @see AppUserProfile - The AppUserProfile model used to interact with user profile data in the database. + * @see UserFamily - The UserFamily model used to interact with user family data in the database. + * @see superAdminCheck - A utility function to check if the user is a super admin. */ export const createUserFamily: MutationResolvers["createUserFamily"] = async ( _parent, @@ -45,7 +59,7 @@ export const createUserFamily: MutationResolvers["createUserFamily"] = async ( } } - // Checks whether user with _id === args.userId exists. + // Checks whether user with _id === context.userId exists. if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -74,7 +88,8 @@ export const createUserFamily: MutationResolvers["createUserFamily"] = async ( USER_NOT_AUTHORIZED_ERROR.PARAM, ); } - // Check whether the user is super admin. + + // Check whether the user is a super admin. superAdminCheck(currentUserAppProfile as InterfaceAppUserProfile); let validationResultName = { diff --git a/src/resolvers/Mutation/createUserTag.ts b/src/resolvers/Mutation/createUserTag.ts index fdeb252058..1547662e47 100644 --- a/src/resolvers/Mutation/createUserTag.ts +++ b/src/resolvers/Mutation/createUserTag.ts @@ -22,6 +22,27 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Creates a new tag for an organization if the user is authorized to do so. + * + * This resolver performs the following steps: + * + * 1. Verifies that the current user exists and is fetched from the cache or database. + * 2. Checks if the current user has an application profile. + * 3. Ensures the current user is authorized to create a tag by being either a super admin or an admin for the specified organization. + * 4. Checks if the provided organization exists. + * 5. Validates that the parent tag (if provided) belongs to the specified organization. + * 6. Ensures no other tag with the same name exists under the same parent tag. + * 7. Creates a new tag if all validation checks pass. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including the tag details and organization ID. + * @param context - The context object, including the user ID and other necessary context for authorization. + * + * @returns The created tag object. + * + * @remarks This function is intended for creating new tags within an organization and includes validation to ensure the integrity of the tag creation process. + */ export const createUserTag: MutationResolvers["createUserTag"] = async ( _parent, args, @@ -61,7 +82,7 @@ export const createUserTag: MutationResolvers["createUserTag"] = async ( } } - //check whether current User has app profile or not + // Check whether current User has app profile or not if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), diff --git a/src/resolvers/Mutation/createVenue.ts b/src/resolvers/Mutation/createVenue.ts index 9d8bf0816c..f1f7398fdf 100644 --- a/src/resolvers/Mutation/createVenue.ts +++ b/src/resolvers/Mutation/createVenue.ts @@ -16,20 +16,27 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; + /** - * This function enables to create a venue in an organization. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists - * 3. Whether the user is admin or superadmin or not - * 4. If the venue name is missing - * 5. If the same venue already exists in an organization - * @returns Created venue + * Creates a new venue within an organization, if the user has appropriate permissions and the venue does not already exist. + * + * This resolver performs the following checks: + * + * 1. Verifies the existence of the user and fetches their profile. + * 2. Checks if the specified organization exists. + * 3. Ensures the user is authorized to create a venue by verifying their admin or superadmin status within the organization. + * 4. Validates that a venue name is provided. + * 5. Ensures that no venue with the same name already exists within the organization. + * 6. Uploads an image if provided and associates it with the venue. + * + * @param _parent - The parent object, not used in this resolver. + * @param args - The input arguments for the mutation, including the venue details and organization ID. + * @param context - The context object, including the user ID, API root URL, and other necessary context for authorization and image upload. + * + * @returns The created venue object, including the associated organization. + * + * @remarks This function includes validation for user authorization, venue uniqueness, and handles image uploads if applicable. */ - export const createVenue: MutationResolvers["createVenue"] = async ( _parent, args, @@ -89,7 +96,7 @@ export const createVenue: MutationResolvers["createVenue"] = async ( ); } - // Checks Whether the user is admin or superadmin or not + // Checks whether the user is admin or superadmin. if ( !( organization.admins?.some((admin) => admin._id.equals(context.userId)) || @@ -103,7 +110,7 @@ export const createVenue: MutationResolvers["createVenue"] = async ( ); } - // Check if the venue name provided is empty string + // Check if the venue name provided is an empty string. if (!args.data?.name ?? "") { throw new errors.InputValidationError( requestContext.translate(VENUE_NAME_MISSING_ERROR.MESSAGE), @@ -112,7 +119,7 @@ export const createVenue: MutationResolvers["createVenue"] = async ( ); } - // Check if a venue with the same organizationId and name exists + // Check if a venue with the same organizationId and name exists. const existingVenue = await Venue.findOne({ name: args.data.name, organization: args.data.organizationId, diff --git a/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts b/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts index e3de027948..9419fb9557 100644 --- a/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts +++ b/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts @@ -1,7 +1,6 @@ import { FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MADE_PLEDGE_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceUser } from "../../models"; @@ -67,16 +66,6 @@ export const removeFundraisingCampaignPledge: MutationResolvers["removeFundraisi ); } - // Checks whether the user has made the pledge. - const pledgeUserIds = pledge.users.map((id) => id?.toString()); - if (!pledgeUserIds.includes(context.userId)) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_MADE_PLEDGE_ERROR.MESSAGE), - USER_NOT_MADE_PLEDGE_ERROR.CODE, - USER_NOT_MADE_PLEDGE_ERROR.PARAM, - ); - } - // Remove the pledge from the campaign. for (const campaignId of pledge.campaigns) { await FundraisingCampaign.updateOne( diff --git a/src/resolvers/Mutation/updateAgendaItem.ts b/src/resolvers/Mutation/updateAgendaItem.ts index 8cd7d0edf5..9965c088cf 100644 --- a/src/resolvers/Mutation/updateAgendaItem.ts +++ b/src/resolvers/Mutation/updateAgendaItem.ts @@ -15,7 +15,10 @@ import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheApp import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import type { + MutationResolvers, + UpdateAgendaItemInput, +} from "../../types/generatedGraphQLTypes"; /** * This function allows the user who created an agenda item to update it. @@ -29,16 +32,13 @@ export const updateAgendaItem: MutationResolvers["updateAgendaItem"] = async ( args, context, ) => { - const userId = args.input.updatedBy; - console.log(context); - // Fetch the current user based on the provided ID let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([userId]); + const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; if (currentUser === null) { currentUser = await User.findOne({ - _id: userId, + _id: context.userId, }).lean(); if (currentUser !== null) { await cacheUsers([currentUser]); @@ -98,12 +98,13 @@ export const updateAgendaItem: MutationResolvers["updateAgendaItem"] = async ( } // Update the agenda item in the database - const updatedAgendaItem = await AgendaItemModel.findOneAndUpdate( - { - _id: args.id, - }, + const updatedAgendaItem = await AgendaItemModel.findByIdAndUpdate( + args.id, { - ...(args.input as InterfaceAgendaItem), + $set: { + ...(args.input as UpdateAgendaItemInput), + }, + updatedBy: context.userId, }, { new: true, // Return the updated document diff --git a/src/resolvers/Mutation/updateFundCampaignPledge.ts b/src/resolvers/Mutation/updateFundCampaignPledge.ts index 1cae234b8f..a21cbc22f7 100644 --- a/src/resolvers/Mutation/updateFundCampaignPledge.ts +++ b/src/resolvers/Mutation/updateFundCampaignPledge.ts @@ -1,7 +1,6 @@ import { FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MADE_PLEDGE_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceUser } from "../../models"; @@ -69,31 +68,22 @@ export const updateFundraisingCampaignPledge: MutationResolvers["updateFundraisi FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR.PARAM, ); } - // console.log(pledge); - //check if user has made the pledge - const pledgeUserIds = pledge.users.map((id) => id?.toString()); - if (!pledgeUserIds.includes(context.userId)) { - throw new errors.ConflictError( - requestContext.translate(USER_NOT_MADE_PLEDGE_ERROR.MESSAGE), - USER_NOT_MADE_PLEDGE_ERROR.CODE, - USER_NOT_MADE_PLEDGE_ERROR.PARAM, - ); - } - - let startDate; - let endDate; + const startDate: Date | undefined = args.data.startDate; + const endDate: Date | undefined = args.data.endDate; + validateDate(startDate, endDate); - if (args.data.startDate) { - startDate = args.data.startDate; - } - if (args.data.endDate) { - endDate = args.data.endDate; + if (args.data.users && args.data.users.length > 0) { + const users = await User.find({ _id: { $in: args.data.users } }).lean(); + if (users.length !== args.data.users.length) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } } - //validates StartDate and endDate - validateDate(startDate, endDate); - // Update the pledge const updatedPledge = await FundraisingCampaignPledge.findOneAndUpdate( { _id: args.id, diff --git a/src/resolvers/Organization/actionItemCategories.ts b/src/resolvers/Organization/actionItemCategories.ts index 37dbe69f26..e84de326a8 100644 --- a/src/resolvers/Organization/actionItemCategories.ts +++ b/src/resolvers/Organization/actionItemCategories.ts @@ -1,9 +1,17 @@ import { ActionItemCategory } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the categories of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all categories of the organization. + * Resolver function for the `actionItemCategories` field of an `Organization`. + * + * This function retrieves the action item categories related to a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @returns A promise that resolves to the action item category documents found in the database. These documents represent the action item categories related to the organization. + * + * @see ActionItemCategory - The ActionItemCategory model used to interact with the action item categories collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const actionItemCategories: OrganizationResolvers["actionItemCategories"] = async (parent) => { diff --git a/src/resolvers/Organization/admins.ts b/src/resolvers/Organization/admins.ts index 87b525a090..ed113e5037 100644 --- a/src/resolvers/Organization/admins.ts +++ b/src/resolvers/Organization/admins.ts @@ -1,9 +1,17 @@ import { User } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the admins of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all admins of the organization. + * Resolver function for the `admins` field of an `Organization`. + * + * This function retrieves the users who are admins of a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the IDs of the users who are admins. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who are admins of the organization. + * + * @see User - The User model used to interact with the users collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const admins: OrganizationResolvers["admins"] = async (parent) => { return await User.find({ diff --git a/src/resolvers/Organization/advertisements.ts b/src/resolvers/Organization/advertisements.ts index 74f7736400..77b3f336e0 100644 --- a/src/resolvers/Organization/advertisements.ts +++ b/src/resolvers/Organization/advertisements.ts @@ -16,10 +16,24 @@ import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; /** - * Resolver function to fetch and return advertisements created in an organization from the database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @param args - Arguments passed to the resolver. - * @returns An object containing an array of advertisements,totalCount of advertisements and pagination information. + * Resolver function for the `advertisements` field of an `Organization`. + * + * This resolver is used to resolve the `advertisements` field of an `Organization` type. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the advertisements. + * @param context - The context object passed to the GraphQL resolvers. It contains the API root URL, which is used to construct the media URL for each advertisement. + * @returns A promise that resolves to a connection object containing the advertisements of the organization. + * + * @see Advertisement - The Advertisement model used to interact with the advertisements collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of advertisements into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of advertisements that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const advertisements: OrganizationResolvers["advertisements"] = async ( parent, @@ -96,10 +110,23 @@ This is typescript type of the parsed cursor for this connection resolver. */ type ParsedCursor = string; -/* -This function is used to validate and transform the cursor passed to this connnection -resolver. -*/ +/** + * Parses the cursor value for the `advertisements` connection resolver. + * + * This function is used to parse the cursor value provided in the arguments of the `advertisements` connection resolver. + * + * @param cursorValue - The cursor value provided in the arguments. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path to the cursor argument in the arguments object. + * @param organizationId - The ID of the organization to which the advertisements belong. + * @returns An object containing the parsed cursor value, or an array of errors if the cursor value is invalid. + * + * @see Advertisement - The Advertisement model used to interact with the advertisements collection in the database. + * @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments of the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. + * + */ export const parseCursor = async ({ cursorValue, cursorName, diff --git a/src/resolvers/Organization/agendaCategories.ts b/src/resolvers/Organization/agendaCategories.ts index 43a9acf48d..159f53bf10 100644 --- a/src/resolvers/Organization/agendaCategories.ts +++ b/src/resolvers/Organization/agendaCategories.ts @@ -1,9 +1,17 @@ import { AgendaCategoryModel } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the categories of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all categories of the organization. + * Resolver function for the `agendaCategories` field of an `Organization`. + * + * This function retrieves the agenda categories of a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @returns A promise that resolves to an array of agenda category documents found in the database. These documents represent the agenda categories of the organization. + * + * @see AgendaCategoryModel - The AgendaCategory model used to interact with the agendaCategories collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const agendaCategories: OrganizationResolvers["agendaCategories"] = async (parent) => { diff --git a/src/resolvers/Organization/blockedUsers.ts b/src/resolvers/Organization/blockedUsers.ts index 70b5a5f9b5..cd6618f7e7 100644 --- a/src/resolvers/Organization/blockedUsers.ts +++ b/src/resolvers/Organization/blockedUsers.ts @@ -1,9 +1,17 @@ import { User } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the blocked users for the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of blocked users for the organization. + * Resolver function for the `blockedUsers` field of an `Organization`. + * + * This function retrieves the users who are blocked by a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the IDs of the users who are blocked. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who are blocked by the organization. + * + * @see User - The User model used to interact with the users collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const blockedUsers: OrganizationResolvers["blockedUsers"] = async ( parent, diff --git a/src/resolvers/Organization/creator.ts b/src/resolvers/Organization/creator.ts index 822d0f6ad8..068faa97c8 100644 --- a/src/resolvers/Organization/creator.ts +++ b/src/resolvers/Organization/creator.ts @@ -2,10 +2,18 @@ import { User } from "../../models"; import { errors, requestContext } from "../../libraries"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; import { USER_NOT_FOUND_ERROR } from "../../constants"; + /** - * This resolver function will fetch and return the creator of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the creator data. If the creator not exists then throws an `NotFoundError` error. + * Resolver function for the `creator` field of an `Organization`. + * + * This function retrieves the user who created a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the organization. + * + * @see User - The User model used to interact with the users collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const creator: OrganizationResolvers["creator"] = async (parent) => { const user = await User.findOne({ diff --git a/src/resolvers/Organization/funds.ts b/src/resolvers/Organization/funds.ts index acdec929f9..18dc4eed9a 100644 --- a/src/resolvers/Organization/funds.ts +++ b/src/resolvers/Organization/funds.ts @@ -2,9 +2,16 @@ import { Fund } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the funds of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An array of objects that contains the funds data. If the funds not exists then returns an empty array. + * Resolver function for the `funds` field of an `Organization`. + * + * This function retrieves the funds related to a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @returns A promise that resolves to the fund documents found in the database. These documents represent the funds related to the organization. + * + * @see Fund - The Fund model used to interact with the funds collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const funds: OrganizationResolvers["funds"] = async (parent) => { return await Fund.find({ diff --git a/src/resolvers/Organization/image.ts b/src/resolvers/Organization/image.ts index c81fedcf60..b94afa9975 100644 --- a/src/resolvers/Organization/image.ts +++ b/src/resolvers/Organization/image.ts @@ -1,6 +1,17 @@ // Context object contains an apiRootUrl for mapping DNS request of server to its domain, for example: http:abcd.com/ import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `image` field of an `Organization`. + * + * This function retrieves the image URL of a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the image URL. + * @returns The URL of the image of the organization. + * + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * + */ export const image: OrganizationResolvers["image"] = ( parent, _args, diff --git a/src/resolvers/Organization/index.ts b/src/resolvers/Organization/index.ts index de297f5a3b..50bc85c30e 100644 --- a/src/resolvers/Organization/index.ts +++ b/src/resolvers/Organization/index.ts @@ -11,6 +11,7 @@ import { membershipRequests } from "./membershipRequests"; import { pinnedPosts } from "./pinnedPosts"; import { posts } from "./posts"; import { advertisements } from "./advertisements"; +import { userTags } from "./userTags"; import { venues } from "./venues"; // import { userTags } from "./userTags"; @@ -27,7 +28,7 @@ export const Organization: OrganizationResolvers = { membershipRequests, pinnedPosts, funds, + userTags, posts, venues, - // userTags, }; diff --git a/src/resolvers/Organization/members.ts b/src/resolvers/Organization/members.ts index f13394077e..1cbff039fb 100644 --- a/src/resolvers/Organization/members.ts +++ b/src/resolvers/Organization/members.ts @@ -2,9 +2,16 @@ import { User } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the list of members of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all members of the organization. + * Resolver function for the `members` field of an `Organization`. + * + * This function retrieves the users who are members of a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the IDs of the users who are members of it. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who are members of the organization. + * + * @see User - The User model used to interact with the users collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const members: OrganizationResolvers["members"] = async (parent) => { return await User.find({ diff --git a/src/resolvers/Organization/membershipRequests.ts b/src/resolvers/Organization/membershipRequests.ts index ebb2433c6d..1af5c31174 100644 --- a/src/resolvers/Organization/membershipRequests.ts +++ b/src/resolvers/Organization/membershipRequests.ts @@ -1,10 +1,17 @@ import { MembershipRequest } from "../../models"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the list of Membership requests for the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @param args - An object that contains relevant data to perform the query. - * @returns An object that contains the list of membership requests for the organization. + * Resolver function for the `membershipRequests` field of an `Organization`. + * + * This function retrieves the membership requests related to a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the IDs of the membership requests. + * @returns A promise that resolves to an array of membership request documents found in the database. These documents represent the membership requests related to the organization. + * + * @see MembershipRequest - The MembershipRequest model used to interact with the membership requests collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const membershipRequests: OrganizationResolvers["membershipRequests"] = async (parent, args) => { diff --git a/src/resolvers/Organization/pinnedPosts.ts b/src/resolvers/Organization/pinnedPosts.ts index e2347ae6ff..46bf4fcf28 100644 --- a/src/resolvers/Organization/pinnedPosts.ts +++ b/src/resolvers/Organization/pinnedPosts.ts @@ -3,6 +3,18 @@ import { cachePosts } from "../../services/PostCache/cachePosts"; import { findPostsInCache } from "../../services/PostCache/findPostsInCache"; import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Resolver function for the `pinnedPosts` field of an `Organization`. + * + * This function retrieves the posts that are pinned by a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the IDs of the posts that are pinned. + * @returns A promise that resolves to the post documents found in the database. These documents represent the posts that are pinned by the organization. + * + * @see Post - The Post model used to interact with the posts collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * + */ export const pinnedPosts: OrganizationResolvers["pinnedPosts"] = async ( parent, ) => { diff --git a/src/resolvers/Organization/posts.ts b/src/resolvers/Organization/posts.ts index bc5c57b916..55f0ba9cae 100644 --- a/src/resolvers/Organization/posts.ts +++ b/src/resolvers/Organization/posts.ts @@ -17,15 +17,24 @@ import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; /** - * Resolver function to fetch and return posts created by a user from the database. - * This function implements cursor-based pagination using GraphQL connection arguments. + * Resolver function for the `posts` field of an `Organization`. * - * @param parent - An object that is the return value of the resolver for this field's parent. - * @param args - Arguments passed to the resolver. These should include pagination arguments such as `first`, `last`, `before`, and `after`. + * This resolver is used to resolve the `posts` field of an `Organization` type. * - * @returns A Promise that resolves to an object containing an array of posts, the total count of posts, and pagination information. The pagination information includes the `startCursor`, `endCursor`, `hasPreviousPage`, and `hasNextPage`. + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the posts. + * @param context - The context object passed to the GraphQL resolvers. It contains the API root URL, which is used to construct the media URL for each post. + * @returns A promise that resolves to a connection object containing the posts of the organization. + * + * @see Post - The Post model used to interact with the posts collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of posts into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of posts that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. * - * @throws GraphQLError Throws an error if the provided arguments are invalid. */ export const posts: OrganizationResolvers["posts"] = async ( parent, @@ -104,14 +113,21 @@ This is typescript type of the parsed cursor for this connection resolver. type ParsedCursor = string; /** - * This function is used to validate and transform the cursor passed to the `posts` connection resolver. + * Parses the cursor value for the `posts` connection resolver. + * + * This function is used to parse the cursor value for the `posts` connection resolver. * - * @param args - An object that includes the cursor value, cursor name, cursor path, and the ID of the creator. + * @param cursorValue - The cursor value to be parsed. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path of the cursor argument in the GraphQL query. + * @param organization - The ID of the organization to which the posts belong. + * @returns An object containing the parsed cursor value or an array of errors if the cursor is invalid. * - * @returns A Promise that resolves to an object that includes a boolean indicating whether the operation was successful, and the parsed cursor value. If the operation was not successful, the object also includes an array of errors. + * @see Post - The Post model used to interact with the posts collection in the database. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments of the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. * - * @throws Error Throws an error if the provided cursor is invalid. - * */ + */ export const parseCursor = async ({ cursorValue, cursorName, diff --git a/src/resolvers/Organization/userTags.ts b/src/resolvers/Organization/userTags.ts new file mode 100644 index 0000000000..6ccdf71d5b --- /dev/null +++ b/src/resolvers/Organization/userTags.ts @@ -0,0 +1,152 @@ +import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; +import type { InterfaceOrganizationTagUser } from "../../models"; +import { OrganizationTagUser } from "../../models"; +import { + getCommonGraphQLConnectionFilter, + getCommonGraphQLConnectionSort, + parseGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, + type DefaultGraphQLArgumentError, + type ParseGraphQLConnectionCursorArguments, + type ParseGraphQLConnectionCursorResult, +} from "../../utilities/graphQLConnection"; +import { GraphQLError } from "graphql"; +import { MAXIMUM_FETCH_LIMIT } from "../../constants"; +import type { Types } from "mongoose"; + +/** + * Resolver function for the `userTags` field of an `Organization`. + * + * This resolver is used to resolve the `userTags` field of an `Organization` type. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the user tags. + * @returns A promise that resolves to a connection object containing the user tags of the organization. + * + * @see OrganizationTagUser - The OrganizationTagUser model used to interact with the user tags collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of user tags into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of user tags that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * + */ +export const userTags: OrganizationResolvers["userTags"] = async ( + parent, + args, +) => { + const parseGraphQLConnectionArgumentsResult = + await parseGraphQLConnectionArguments({ + args, + parseCursor: (args) => + parseCursor({ + ...args, + organizationId: parent._id, + }), + maximumLimit: MAXIMUM_FETCH_LIMIT, + }); + + if (!parseGraphQLConnectionArgumentsResult.isSuccessful) { + throw new GraphQLError("Invalid arguments provided.", { + extensions: { + code: "INVALID_ARGUMENTS", + errors: parseGraphQLConnectionArgumentsResult.errors, + }, + }); + } + + const { parsedArgs } = parseGraphQLConnectionArgumentsResult; + + const filter = getCommonGraphQLConnectionFilter({ + cursor: parsedArgs.cursor, + direction: parsedArgs.direction, + }); + + const sort = getCommonGraphQLConnectionSort({ + direction: parsedArgs.direction, + }); + + const [objectList, totalCount] = await Promise.all([ + OrganizationTagUser.find({ + ...filter, + organizationId: parent._id, + }) + .sort(sort) + .limit(parsedArgs.limit) + .lean() + .exec(), + OrganizationTagUser.find({ + organizationId: parent._id, + }) + .countDocuments() + .exec(), + ]); + + return transformToDefaultGraphQLConnection< + ParsedCursor, + InterfaceOrganizationTagUser, + InterfaceOrganizationTagUser + >({ + objectList, + parsedArgs, + totalCount, + }); +}; + +/* +This is typescript type of the parsed cursor for this connection resolver. +*/ +type ParsedCursor = string; + +/** + * Parses the cursor value for the `userTags` connection resolver. + * + * This function is used to parse the cursor value for the `userTags` connection resolver. + * + * @param cursorValue - The cursor value to be parsed. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path of the cursor argument in the query. + * @param organizationId - The ID of the organization to which the user tags belong. + * @returns An object containing the parsed cursor value or an array of errors if the cursor is invalid. + * + * @see OrganizationTagUser - The OrganizationTagUser model used to interact with the user tags collection in the database. + * @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments of the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. + * + */ +export const parseCursor = async ({ + cursorValue, + cursorName, + cursorPath, + organizationId, +}: ParseGraphQLConnectionCursorArguments & { + organizationId: string | Types.ObjectId; +}): ParseGraphQLConnectionCursorResult => { + const errors: DefaultGraphQLArgumentError[] = []; + const tag = await OrganizationTagUser.findOne({ + _id: cursorValue, + organizationId, + }); + + if (!tag) { + errors.push({ + message: `Argument ${cursorName} is an invalid cursor.`, + path: cursorPath, + }); + } + + if (errors.length !== 0) { + return { + errors, + isSuccessful: false, + }; + } + + return { + isSuccessful: true, + parsedCursor: cursorValue, + }; +}; diff --git a/src/resolvers/Organization/venues.ts b/src/resolvers/Organization/venues.ts index e283977c7a..e1d7af0b51 100644 --- a/src/resolvers/Organization/venues.ts +++ b/src/resolvers/Organization/venues.ts @@ -1,9 +1,17 @@ import type { OrganizationResolvers } from "./../../types/generatedGraphQLTypes"; import { Venue } from "../../models"; + /** - * This resolver will fetch the list of all venues within an Organization from database. - * @param parent- An object that contains `id` of the organization. - * @returns An array that contains the venues. + * Resolver function for the `venues` field of an `Organization`. + * + * This function retrieves the venues related to a specific organization. + * + * @param parent - The parent object representing the organization. It contains information about the organization, including the ID of the organization. + * @returns A promise that resolves to the venue documents found in the database. These documents represent the venues related to the organization. + * + * @see Venue - The Venue model used to interact with the venues collection in the database. + * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. + * */ export const venues: OrganizationResolvers["venues"] = async (parent) => { return await Venue.find({ organization: parent._id.toString() }).lean(); diff --git a/src/resolvers/Post/comments.ts b/src/resolvers/Post/comments.ts index 2fa0abd431..3672aad776 100644 --- a/src/resolvers/Post/comments.ts +++ b/src/resolvers/Post/comments.ts @@ -3,6 +3,18 @@ import { Comment } from "../../models"; import { cacheComments } from "../../services/CommentCache/cacheComments"; import { findCommentsByPostIdInCache } from "../../services/CommentCache/findCommentsByPostIdInCache"; +/** + * Resolver function for the `comments` field of a `Post`. + * + * This function retrieves the comments associated with a specific post. + * + * @param parent - The parent object representing the post. It contains information about the post, including the ID of the comments associated with it. + * @returns A promise that resolves to an array of comment documents found in the database. These documents represent the comments associated with the post. + * + * @see Comment - The Comment model used to interact with the comments collection in the database. + * @see PostResolvers - The type definition for the resolvers of the Post fields. + * + */ export const comments: PostResolvers["comments"] = async (parent) => { const commentsInCache = await findCommentsByPostIdInCache(parent._id); diff --git a/src/resolvers/Post/creator.ts b/src/resolvers/Post/creator.ts index a365a179d7..d9a7b6e507 100644 --- a/src/resolvers/Post/creator.ts +++ b/src/resolvers/Post/creator.ts @@ -1,6 +1,18 @@ import type { PostResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; +/** + * Resolver function for the `creator` field of a `Post`. + * + * This function retrieves the user who created a specific post. + * + * @param parent - The parent object representing the post. It contains information about the post, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the post. + * + * @see User - The User model used to interact with the users collection in the database. + * @see PostResolvers - The type definition for the resolvers of the Post fields. + * + */ export const creator: PostResolvers["creator"] = async (parent) => { return await User.findOne({ _id: parent.creatorId, diff --git a/src/resolvers/Query/agendaItemByEvent.ts b/src/resolvers/Query/agendaItemByEvent.ts new file mode 100644 index 0000000000..118ea2b1d9 --- /dev/null +++ b/src/resolvers/Query/agendaItemByEvent.ts @@ -0,0 +1,19 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { AgendaItemModel } from "../../models"; + +/** + * This query will fetch all items for the organization from database. + * @param _parent- + * @param args - An object that contains `organizationId` which is the _id of the Organization. + * @returns A `Item` object that holds all Item for the Organization. + */ +export const agendaItemByEvent: QueryResolvers["agendaItemByEvent"] = async ( + _parent, + args, +) => { + return await AgendaItemModel.find({ + relatedEventId: args.relatedEventId, + }) + .sort({ sequence: 1 }) + .lean(); +}; diff --git a/src/resolvers/Query/agendaItemByOrganization.ts b/src/resolvers/Query/agendaItemByOrganization.ts new file mode 100644 index 0000000000..65fe647b26 --- /dev/null +++ b/src/resolvers/Query/agendaItemByOrganization.ts @@ -0,0 +1,15 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { AgendaItemModel } from "../../models"; + +/** + * This query will fetch all items for the organization from database. + * @param _parent- + * @param args - An object that contains `organizationId` which is the _id of the Organization. + * @returns A `Item` object that holds all Item for the Organization. + */ +export const agendaItemByOrganization: QueryResolvers["agendaItemByOrganization"] = + async (_parent, args) => { + return await AgendaItemModel.find({ + organizationId: args.organizationId, + }).lean(); + }; diff --git a/src/resolvers/Query/directChatById.ts b/src/resolvers/Query/directChatById.ts new file mode 100644 index 0000000000..00a4b9f852 --- /dev/null +++ b/src/resolvers/Query/directChatById.ts @@ -0,0 +1,31 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { errors } from "../../libraries"; +import { DirectChat } from "../../models"; +import { CHAT_NOT_FOUND_ERROR } from "../../constants"; + +/** + * This query will fetch all messages for a certain direct chat for the user from database. + * @param _parent- + * @param args - An object that contains `id` of the direct chat. + * @returns A `directChatsMessages` object that holds all of the messages from the specified direct chat. + * If the `directChatsMessages` object is null then it throws `NotFoundError` error. + * @remarks You can learn about GraphQL `Resolvers` + * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. + */ + +export const directChatById: QueryResolvers["directChatById"] = async ( + _parent, + args, +) => { + const directChat = await DirectChat.findById(args.id).lean(); + + if (!directChat) { + throw new errors.NotFoundError( + CHAT_NOT_FOUND_ERROR.DESC, + CHAT_NOT_FOUND_ERROR.CODE, + CHAT_NOT_FOUND_ERROR.PARAM, + ); + } + + return directChat; +}; diff --git a/src/resolvers/Query/fundsByOrganization.ts b/src/resolvers/Query/fundsByOrganization.ts index 45d70baa79..11628fd1c7 100644 --- a/src/resolvers/Query/fundsByOrganization.ts +++ b/src/resolvers/Query/fundsByOrganization.ts @@ -1,16 +1,18 @@ import type { InterfaceFund } from "../../models"; import { Fund } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { getSort } from "./helperFunctions/getSort"; import { getWhere } from "./helperFunctions/getWhere"; export const fundsByOrganization: QueryResolvers["fundsByOrganization"] = async (_parent, args) => { const where = getWhere(args.where); + const sort = getSort(args.orderBy); const funds = await Fund.find({ organizationId: args.organizationId, ...where, - }); + }).sort(sort); return funds; }; diff --git a/src/resolvers/Query/getFundById.ts b/src/resolvers/Query/getFundById.ts index 2b65396e77..27a4ea43c0 100644 --- a/src/resolvers/Query/getFundById.ts +++ b/src/resolvers/Query/getFundById.ts @@ -1,6 +1,8 @@ import type { InterfaceFund } from "../../models"; import { Fund } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { getSort } from "./helperFunctions/getSort"; +import { getWhere } from "./helperFunctions/getWhere"; /** * This query will fetch the fund as a transaction from database. @@ -12,9 +14,21 @@ export const getFundById: QueryResolvers["getFundById"] = async ( _parent, args, ) => { + const sort = getSort(args.orderBy); + const where = getWhere(args.where); const fund = await Fund.findOne({ _id: args.id, - }).lean(); + }) + .populate({ + path: "campaigns", + match: { + ...where, + }, + options: { + sort: sort, + }, + }) + .lean(); return fund ?? ({} as InterfaceFund); }; diff --git a/src/resolvers/Query/getFundraisingCampaign.ts b/src/resolvers/Query/getFundraisingCampaign.ts index 6ef431878d..deb2dd362c 100644 --- a/src/resolvers/Query/getFundraisingCampaign.ts +++ b/src/resolvers/Query/getFundraisingCampaign.ts @@ -3,6 +3,7 @@ import { type InterfaceFundraisingCampaign, } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { getSort } from "./helperFunctions/getSort"; /** * This query will fetch the fundraisingCampaign as a transaction from database. * @param _parent- @@ -11,9 +12,21 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; */ //@ts-expect-error - type error export const getFundraisingCampaignById: QueryResolvers["getFundraisingCampaignById"] = async (_parent, args) => { + const sort = getSort(args.orderBy); const campaign = await FundraisingCampaign.findOne({ _id: args.id, - }).lean(); + }) + .populate("fundId") + .populate({ + path: "pledges", + populate: { + path: "users", + }, + options: { + sort: sort, + }, + }) + .lean(); return campaign ?? ({} as InterfaceFundraisingCampaign); }; diff --git a/src/resolvers/Query/groupChatById.ts b/src/resolvers/Query/groupChatById.ts new file mode 100644 index 0000000000..43a00ce65d --- /dev/null +++ b/src/resolvers/Query/groupChatById.ts @@ -0,0 +1,31 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { errors } from "../../libraries"; +import { GroupChat } from "../../models"; +import { CHAT_NOT_FOUND_ERROR } from "../../constants"; + +/** + * This query will fetch all messages for a certain direct chat for the user from database. + * @param _parent- + * @param args - An object that contains `id` of the direct chat. + * @returns A `directChatsMessages` object that holds all of the messages from the specified direct chat. + * If the `directChatsMessages` object is null then it throws `NotFoundError` error. + * @remarks You can learn about GraphQL `Resolvers` + * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. + */ + +export const groupChatById: QueryResolvers["groupChatById"] = async ( + _parent, + args, +) => { + const directChat = await GroupChat.findById(args.id).lean(); + + if (!directChat) { + throw new errors.NotFoundError( + CHAT_NOT_FOUND_ERROR.DESC, + CHAT_NOT_FOUND_ERROR.CODE, + CHAT_NOT_FOUND_ERROR.PARAM, + ); + } + + return directChat; +}; diff --git a/src/resolvers/Query/groupChatsByUserId.ts b/src/resolvers/Query/groupChatsByUserId.ts new file mode 100644 index 0000000000..a3b3310d3e --- /dev/null +++ b/src/resolvers/Query/groupChatsByUserId.ts @@ -0,0 +1,30 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { errors } from "../../libraries"; +import { GroupChat } from "../../models"; +/** + * This query will fetch all the Direct chats for the current user from the database. + * @param _parent- + * @param args - An object that contains `id` of the user. + * @returns An object `GroupChat` that contains all direct chats of the current user. + * If the `directChats` object is null then it throws `NotFoundError` error. + * @remarks You can learn about GraphQL `Resolvers` + * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. + */ +export const groupChatsByUserId: QueryResolvers["groupChatsByUserId"] = async ( + _parent, + args, +) => { + const groupChats = await GroupChat.find({ + users: args.id, + }).lean(); + + if (groupChats.length === 0) { + throw new errors.NotFoundError( + "Group Chats not found", + "groupChats.notFound", + "groupChats", + ); + } + + return groupChats; +}; diff --git a/src/resolvers/Query/helperFunctions/getSort.ts b/src/resolvers/Query/helperFunctions/getSort.ts index 8793eef2a0..6ebaeeac7c 100644 --- a/src/resolvers/Query/helperFunctions/getSort.ts +++ b/src/resolvers/Query/helperFunctions/getSort.ts @@ -6,6 +6,9 @@ import type { PostOrderByInput, UserOrderByInput, VenueOrderByInput, + PledgeOrderByInput, + CampaignOrderByInput, + FundOrderByInput, } from "../../../types/generatedGraphQLTypes"; export const getSort = ( @@ -16,6 +19,9 @@ export const getSort = ( | PostOrderByInput | UserOrderByInput | VenueOrderByInput + | FundOrderByInput + | CampaignOrderByInput + | PledgeOrderByInput > | undefined, ): @@ -75,6 +81,18 @@ export const getSort = ( }; break; + case "amount_ASC": + sortPayload = { + amount: 1, + }; + break; + + case "amount_DESC": + sortPayload = { + amount: -1, + }; + break; + case "startDate_ASC": sortPayload = { startDate: 1, @@ -99,6 +117,18 @@ export const getSort = ( }; break; + case "fundingGoal_ASC": + sortPayload = { + fundingGoal: 1, + }; + break; + + case "fundingGoal_DESC": + sortPayload = { + fundingGoal: -1, + }; + break; + case "allDay_ASC": sortPayload = { allDay: 1, diff --git a/src/resolvers/Query/helperFunctions/getWhere.ts b/src/resolvers/Query/helperFunctions/getWhere.ts index b09b26ee47..9c5c795460 100644 --- a/src/resolvers/Query/helperFunctions/getWhere.ts +++ b/src/resolvers/Query/helperFunctions/getWhere.ts @@ -9,6 +9,7 @@ import type { PostWhereInput, UserWhereInput, VenueWhereInput, + CampaignWhereInput, } from "../../../types/generatedGraphQLTypes"; /** @@ -34,6 +35,7 @@ export const getWhere = ( UserWhereInput & DonationWhereInput & ActionItemWhereInput & + CampaignWhereInput & FundWhereInput & VenueWhereInput > diff --git a/src/resolvers/Query/index.ts b/src/resolvers/Query/index.ts index 3f67d7597a..55c74a7bca 100644 --- a/src/resolvers/Query/index.ts +++ b/src/resolvers/Query/index.ts @@ -5,6 +5,8 @@ import { actionItemsByEvent } from "./actionItemsByEvent"; import { actionItemsByOrganization } from "./actionItemsByOrganization"; import { advertisementsConnection } from "./advertisementsConnection"; import { agendaCategory } from "./agendaCategory"; +import { agendaItemByEvent } from "./agendaItemByEvent"; +import { agendaItemByOrganization } from "./agendaItemByOrganization"; import { agendaItemCategoriesByOrganization } from "./agendaItemCategoriesByOrganization"; import { getAgendaItem } from "./agendaItemById"; import { getAgendaSection } from "./getAgendaSection"; @@ -13,6 +15,9 @@ import { customDataByOrganization } from "./customDataByOrganization"; import { customFieldsByOrganization } from "./customFieldsByOrganization"; import { directChatsByUserID } from "./directChatsByUserID"; import { directChatsMessagesByChatID } from "./directChatsMessagesByChatID"; +import { directChatById } from "./directChatById"; +import { groupChatById } from "./groupChatById"; +import { groupChatsByUserId } from "./groupChatsByUserId"; import { event } from "./event"; import { eventsByOrganization } from "./eventsByOrganization"; import { eventsByOrganizationConnection } from "./eventsByOrganizationConnection"; @@ -52,6 +57,8 @@ export const Query: QueryResolvers = { getAllAgendaItems, actionItemsByOrganization, actionItemCategoriesByOrganization, + agendaItemByEvent, + agendaItemByOrganization, agendaItemCategoriesByOrganization, checkAuth, getCommunityData, @@ -59,6 +66,9 @@ export const Query: QueryResolvers = { customDataByOrganization, directChatsByUserID, directChatsMessagesByChatID, + directChatById, + groupChatById, + groupChatsByUserId, event, eventsByOrganization, eventsByOrganizationConnection, diff --git a/src/resolvers/RecurrenceRule/baseRecurringEvent.ts b/src/resolvers/RecurrenceRule/baseRecurringEvent.ts index 48b90afab1..35200640e8 100644 --- a/src/resolvers/RecurrenceRule/baseRecurringEvent.ts +++ b/src/resolvers/RecurrenceRule/baseRecurringEvent.ts @@ -1,6 +1,18 @@ import type { RecurrenceRuleResolvers } from "../../types/generatedGraphQLTypes"; import { Event } from "../../models"; +/** + * Resolver function for the `baseRecurringEvent` field of a `RecurrenceRule`. + * + * This function retrieves the base recurring event associated with a specific recurrence rule. + * + * @param parent - The parent object representing the recurrence rule. It contains information about the recurrence rule, including the ID of the base recurring event associated with it. + * @returns A promise that resolves to the event document found in the database. This document represents the base recurring event associated with the recurrence rule. + * + * @see Event - The Event model used to interact with the events collection in the database. + * @see RecurrenceRuleResolvers - The type definition for the resolvers of the RecurrenceRule fields. + * + */ export const baseRecurringEvent: RecurrenceRuleResolvers["baseRecurringEvent"] = async (parent) => { return await Event.findOne({ diff --git a/src/resolvers/RecurrenceRule/organization.ts b/src/resolvers/RecurrenceRule/organization.ts index b17690cb7e..6266884482 100644 --- a/src/resolvers/RecurrenceRule/organization.ts +++ b/src/resolvers/RecurrenceRule/organization.ts @@ -1,6 +1,19 @@ import type { RecurrenceRuleResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; +/** + * Resolver function for the `organization` field of a `RecurrenceRule`. + * + * This function retrieves the organization associated with a specific recurrence rule. + * + * @param parent - The parent object representing the recurrence rule. It contains information about the recurrence rule, including the ID of the organization associated with it. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the recurrence rule. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see RecurrenceRuleResolvers - The type definition for the resolvers of the RecurrenceRule fields. + * + */ + export const organization: RecurrenceRuleResolvers["organization"] = async ( parent, ) => { diff --git a/src/resolvers/Subscription/messageSentToDirectChat.ts b/src/resolvers/Subscription/messageSentToDirectChat.ts index bd937b051f..55ce56e603 100644 --- a/src/resolvers/Subscription/messageSentToDirectChat.ts +++ b/src/resolvers/Subscription/messageSentToDirectChat.ts @@ -4,11 +4,16 @@ import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; const MESSAGE_SENT_TO_DIRECT_CHAT = "MESSAGE_SENT_TO_DIRECT_CHAT"; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const filterFunction = function (payload: any, context: any): boolean { - const { currentUserId } = context.context; +export const filterFunction = function (payload: any, variables: any): boolean { + const currentUserId = variables.userId.toString(); + console.log( + currentUserId, + payload.messageSentToDirectChat.receiver.toString(), + payload.messageSentToDirectChat.sender.toString(), + ); return ( - currentUserId === payload.messageSentToDirectChat.receiver || - currentUserId === payload.messageSentToDirectChat.sender + currentUserId === payload.messageSentToDirectChat.receiver.toString() || + currentUserId === payload.messageSentToDirectChat.sender.toString() ); }; /** @@ -26,6 +31,6 @@ export const messageSentToDirectChat: SubscriptionResolvers["messageSentToDirect (_parent, _args, context) => context.pubsub.asyncIterator([MESSAGE_SENT_TO_DIRECT_CHAT]), - (payload, _variables, context) => filterFunction(payload, context), + (payload, variables) => filterFunction(payload, variables), ), }; diff --git a/src/resolvers/Subscription/messageSentToGroupChat.ts b/src/resolvers/Subscription/messageSentToGroupChat.ts index 0d4c56fa59..ec45eb30c2 100644 --- a/src/resolvers/Subscription/messageSentToGroupChat.ts +++ b/src/resolvers/Subscription/messageSentToGroupChat.ts @@ -5,11 +5,18 @@ import { GroupChat } from "../../models"; const MESSAGE_SENT_TO_GROUP_CHAT = "MESSAGE_SENT_TO_GROUP_CHAT"; +/** + * This function is used to filter the subscription payload based on the current user's membership in the group chat. + * + * @param payload - The payload of the subscription message. + * @param context - The context object containing the current user's ID. + * @returns A promise that resolves to a boolean value indicating whether the current user is a member of the group chat. + */ export const filterFunction = async function ( payload: any, - context: any, + variables: any, ): Promise { - const { currentUserId } = context.context; + const currentUserId = variables.userId; const groupChatId = payload.messageSentToGroupChat.groupChatMessageBelongsTo; const groupChat = await GroupChat.findOne({ @@ -40,6 +47,6 @@ export const messageSentToGroupChat: SubscriptionResolvers["messageSentToGroupCh (_parent, _args, context) => context.pubsub.asyncIterator([MESSAGE_SENT_TO_GROUP_CHAT]), - (payload, _variables, context) => filterFunction(payload, context), + (payload, variables) => filterFunction(payload, variables), ), }; diff --git a/src/resolvers/Subscription/onPluginUpdate.ts b/src/resolvers/Subscription/onPluginUpdate.ts index f4744845dc..848bbaf648 100644 --- a/src/resolvers/Subscription/onPluginUpdate.ts +++ b/src/resolvers/Subscription/onPluginUpdate.ts @@ -25,9 +25,25 @@ export const filterFunction = async function ( ): Promise { return true; }; + +/** + * This function creates a response object for the `onPluginUpdate` subscription. + * + * @param payload - The payload received from the subscription. + * @returns The response object for the subscription. + */ export const createPluginUpdateResponse = (payload: any): any => { return payload.Plugin; }; + +/** + * This property included a `subscribe` method, which is used to + * subscribe the `current_user` to get updates for Group chats. + * + * @remarks To control updates on a per-client basis, the function uses the `withFilter` + * method imported from `apollo-server-express` module. + * You can learn about `subscription` {@link https://www.apollographql.com/docs/apollo-server/data/subscriptions/ | here }. + */ export const onPluginUpdate: SubscriptionResolvers["onPluginUpdate"] = { // @ts-expect-error-ts-ignore subscribe: withFilter( diff --git a/src/resolvers/UserFamily/admins.ts b/src/resolvers/UserFamily/admins.ts index 9571bd0c02..0eab116e98 100644 --- a/src/resolvers/UserFamily/admins.ts +++ b/src/resolvers/UserFamily/admins.ts @@ -1,9 +1,17 @@ import { User } from "../../models"; import type { UserFamilyResolvers } from "../../types/generatedGraphQLTypes"; + /** - * This resolver function will fetch and return the admins of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the list of all admins of the organization. + * Resolver function for the `admins` field of a `UserFamily`. + * + * This function retrieves the users who are admins of a specific user family. + * + * @param parent - The parent object representing the user family. It contains information about the user family, including the IDs of the users who are admins. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who are admins of the user family. + * + * @see User - The User model used to interact with the users collection in the database. + * @see UserFamilyResolvers - The type definition for the resolvers of the UserFamily fields. + * */ export const admins: UserFamilyResolvers["admins"] = async (parent) => { return await User.find({ diff --git a/src/resolvers/UserFamily/creator.ts b/src/resolvers/UserFamily/creator.ts index 69fb4b7e3b..1e0ec81727 100644 --- a/src/resolvers/UserFamily/creator.ts +++ b/src/resolvers/UserFamily/creator.ts @@ -2,10 +2,18 @@ import { User } from "../../models"; import { errors, requestContext } from "../../libraries"; import type { UserFamilyResolvers } from "../../types/generatedGraphQLTypes"; import { USER_NOT_FOUND_ERROR } from "../../constants"; + /** - * This resolver function will fetch and return the creator of the Organization from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An object that contains the creator data. If the creator not exists then throws an `NotFoundError` error. + * Resolver function for the `creator` field of a `UserFamily`. + * + * This function retrieves the user who created a specific user family. + * + * @param parent - The parent object representing the user family. It contains information about the user family, including the ID of the user who created it. + * @returns A promise that resolves to the user document found in the database. This document represents the user who created the user family. + * + * @see User - The User model used to interact with the users collection in the database. + * @see UserFamilyResolvers - The type definition for the resolvers of the UserFamily fields. + * */ export const creator: UserFamilyResolvers["creator"] = async (parent) => { const user = await User.findOne({ diff --git a/src/resolvers/UserFamily/users.ts b/src/resolvers/UserFamily/users.ts index c02ba909f4..2a139731b7 100644 --- a/src/resolvers/UserFamily/users.ts +++ b/src/resolvers/UserFamily/users.ts @@ -1,9 +1,17 @@ import type { UserFamilyResolvers } from "../../types/generatedGraphQLTypes"; import { User } from "../../models"; + /** - * This resolver function will fetch and return the list of all Members of the user family from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains the Member data. + * Resolver function for the `users` field of a `UserFamily`. + * + * This function retrieves the users who are part of a specific user family. + * + * @param parent - The parent object representing the user family. It contains information about the user family, including the IDs of the users who are part of it. + * @returns A promise that resolves to an array of user documents found in the database. These documents represent the users who are part of the user family. + * + * @see User - The User model used to interact with the users collection in the database. + * @see UserFamilyResolvers - The type definition for the resolvers of the UserFamily fields. + * */ export const users: UserFamilyResolvers["users"] = async (parent) => { return await User.find({ diff --git a/src/resolvers/UserTag/childTags.ts b/src/resolvers/UserTag/childTags.ts index 15704f6ab3..ae95fb2b16 100644 --- a/src/resolvers/UserTag/childTags.ts +++ b/src/resolvers/UserTag/childTags.ts @@ -14,6 +14,25 @@ import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; import type { Types } from "mongoose"; +/** + * Resolver function for the `childTags` field of a `UserTag`. + * + * This resolver is used to resolve the `childTags` field of a `UserTag` type. + * + * @param parent - The parent object representing the user tag. It contains information about the user tag, including the ID of the user tag. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the child tags. + * @returns A promise that resolves to a connection object containing the child tags of the user tag. + * + * @see OrganizationTagUser - The OrganizationTagUser model used to interact with the organization tag users collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of child tags into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of child tags that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see UserTagResolvers - The type definition for the resolvers of the UserTag fields. + * + */ export const childTags: UserTagResolvers["childTags"] = async ( parent, args, diff --git a/src/resolvers/UserTag/organization.ts b/src/resolvers/UserTag/organization.ts index b38ead0540..f14406a047 100644 --- a/src/resolvers/UserTag/organization.ts +++ b/src/resolvers/UserTag/organization.ts @@ -1,6 +1,18 @@ import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; import { Organization } from "../../models"; +/** + * Resolver function for the `organization` field of a `UserTag`. + * + * This function retrieves the organization associated with a specific user tag. + * + * @param parent - The parent object representing the user tag. It contains information about the user tag, including the ID of the organization associated with it. + * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the user tag. + * + * @see Organization - The Organization model used to interact with the organizations collection in the database. + * @see UserTagResolvers - The type definition for the resolvers of the UserTag fields. + * + */ export const organization: UserTagResolvers["organization"] = async ( parent, ) => { diff --git a/src/resolvers/UserTag/parentTag.ts b/src/resolvers/UserTag/parentTag.ts index 29eb9f99ae..d4ecbd76b0 100644 --- a/src/resolvers/UserTag/parentTag.ts +++ b/src/resolvers/UserTag/parentTag.ts @@ -1,6 +1,18 @@ import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; import { OrganizationTagUser } from "../../models"; +/** + * Resolver function for the `parentTag` field of a `UserTag`. + * + * This function retrieves the parent tag associated with a specific user tag. + * + * @param parent - The parent object representing the user tag. It contains information about the user tag, including the ID of the parent tag associated with it. + * @returns A promise that resolves to the user tag document found in the database. This document represents the parent tag associated with the user tag. + * + * @see OrganizationTagUser - The OrganizationTagUser model used to interact with the user tags collection in the database. + * @see UserTagResolvers - The type definition for the resolvers of the UserTag fields. + * + */ export const parentTag: UserTagResolvers["parentTag"] = async (parent) => { // Check if the parentTag is null if (parent.parentTagId === null) return null; diff --git a/src/resolvers/UserTag/usersAssignedTo.ts b/src/resolvers/UserTag/usersAssignedTo.ts index ade6da58c0..ebc8dd20a6 100644 --- a/src/resolvers/UserTag/usersAssignedTo.ts +++ b/src/resolvers/UserTag/usersAssignedTo.ts @@ -14,6 +14,25 @@ import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; import type { Types } from "mongoose"; +/** + * Resolver function for the `usersAssignedTo` field of a `UserTag`. + * + * This resolver is used to resolve the `usersAssignedTo` field of a `UserTag` type. + * + * @param parent - The parent object representing the user tag. It contains information about the user tag, including the ID of the user tag. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the users assigned to the user tag. + * @returns A promise that resolves to a connection object containing the users assigned to the user tag. + * + * @see TagUser - The TagUser model used to interact with the tag users collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of users assigned to the user tag into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of users that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see UserTagResolvers - The type definition for the resolvers of the UserTag fields. + * + */ export const usersAssignedTo: UserTagResolvers["usersAssignedTo"] = async ( parent, args, @@ -86,10 +105,23 @@ This is typescript type of the parsed cursor for this connection resolver. */ type ParsedCursor = string; -/* -This function is used to validate and transform the cursor passed to this connnection -resolver. -*/ +/** + * Parses the cursor value for the `usersAssignedTo` connection resolver. + * + * This function is used to parse the cursor value provided to the `usersAssignedTo` connection resolver. + * + * @param cursorValue - The cursor value to be parsed. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path of the cursor argument in the GraphQL query. + * @param tagId - The ID of the user tag to which the users are assigned. + * @returns An object containing the parsed cursor value or an array of errors if the cursor value is invalid. + * + * @see TagUser - The TagUser model used to interact with the tag users collection in the database. + * @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments provided to the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. + * + */ export const parseCursor = async ({ cursorValue, cursorName, diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index a17e87c9bd..01f58e313a 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -25,7 +25,6 @@ import { Event } from "./Event"; import { EventVolunteer } from "./EventVolunteer"; import { Feedback } from "./Feedback"; import { Fund } from "./Fund"; -import { FundraisingCampaign } from "./FundraisingCampagin"; import { GroupChat } from "./GroupChat"; import { GroupChatMessage } from "./GroupChatMessage"; import { MembershipRequest } from "./MembershipRequest"; @@ -59,7 +58,6 @@ const resolvers: Resolvers = { EventVolunteer, Feedback, Fund, - FundraisingCampaign, GroupChat, UserFamily, GroupChatMessage, diff --git a/src/resolvers/middleware/currentUserExists.ts b/src/resolvers/middleware/currentUserExists.ts index 39fcb362aa..9aec9404fe 100644 --- a/src/resolvers/middleware/currentUserExists.ts +++ b/src/resolvers/middleware/currentUserExists.ts @@ -4,7 +4,19 @@ import { User } from "../../models"; import { USER_NOT_FOUND_ERROR } from "../../constants"; import { errors, requestContext } from "../../libraries"; -// This is a middleware that checks that the user specificied by the `userId` parameter in the context does indeed exist in the database +/** + * Middleware function to check if the current user exists in the database. + * + * This function is used to check if the user making a request to the server exists in the database. + * If the user does not exist, the function throws an error. + * + * @param next - The next function to call in the resolver chain. + * @returns The result of the next function in the resolver chain. + * + * @see User - The User model used to interact with the users collection in the database. + * @see USER_NOT_FOUND_ERROR - The error message to display when the user is not found. + * @see errors - The library used to create custom errors in the application. + */ export const currentUserExists = () => (next: (root: any, args: any, context: any, info: any) => any) => diff --git a/src/services/AppUserProfileCache/cacheAppUserProfile.ts b/src/services/AppUserProfileCache/cacheAppUserProfile.ts index 977079390e..afeb79f7b1 100644 --- a/src/services/AppUserProfileCache/cacheAppUserProfile.ts +++ b/src/services/AppUserProfileCache/cacheAppUserProfile.ts @@ -2,21 +2,34 @@ import { logger } from "../../libraries"; import type { InterfaceAppUserProfile } from "../../models"; import AppUserCache from "../redisCache"; +/** + * Stores app user profiles in Redis cache with a specified time-to-live (TTL). + * @param appUserProfiles - Array of app user profiles to be cached. + * @returns Promise + */ export async function cacheAppUserProfile( appUserProfiles: InterfaceAppUserProfile[], ): Promise { try { + // Create a pipeline for efficient Redis operations const pipeline = AppUserCache.pipeline(); + appUserProfiles.forEach((appUserProfile) => { if (appUserProfile !== null) { + // Generate key for each app user profile based on its ID const key = `appUserProfile:${appUserProfile._id}`; - // Set the appUserProfile in the cache + + // Store app user profile data as JSON string in Redis pipeline.set(key, JSON.stringify(appUserProfile)); - // SET the time to live for each of the appUserProfile in the cache to 300s. + + // Set TTL for each app user profile to 300 seconds (5 minutes) pipeline.expire(key, 300); } }); + + // Execute the pipeline for batch Redis operations } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/AppUserProfileCache/deleteAppUserFromCache.ts b/src/services/AppUserProfileCache/deleteAppUserFromCache.ts index d9ae97ee99..96983ce068 100644 --- a/src/services/AppUserProfileCache/deleteAppUserFromCache.ts +++ b/src/services/AppUserProfileCache/deleteAppUserFromCache.ts @@ -1,8 +1,17 @@ import AppUserCache from "../redisCache"; + +/** + * Deletes the specified app user profile from Redis cache. + * + * @param appUserProfileId - The string representing the app user profile ID to delete from cache. + * @returns A promise resolving to void. + */ export async function deleteAppUserFromCache( appUserProfileId: string, ): Promise { + // Generate the cache key for the app user profile based on its ID const key = `appUserProfile:${appUserProfileId}`; + // Delete the app user profile from Redis cache await AppUserCache.del(key); } diff --git a/src/services/CommentCache/cacheComments.ts b/src/services/CommentCache/cacheComments.ts index bfe9cd6d53..6e5fc1f6c4 100644 --- a/src/services/CommentCache/cacheComments.ts +++ b/src/services/CommentCache/cacheComments.ts @@ -2,30 +2,42 @@ import { logger } from "../../libraries"; import type { InterfaceComment } from "../../models"; import CommentCache from "../redisCache"; -// Function to store comments in the cache using pipelining +/** + * Stores comments in Redis cache with a specified time-to-live (TTL). + * @param comments - Array of comments to be cached. + * @returns Promise + */ export async function cacheComments( comments: InterfaceComment[], ): Promise { try { + // Create a pipeline for efficient Redis operations const pipeline = CommentCache.pipeline(); comments.forEach((comment) => { if (comment !== null) { + // Generate key for each comment based on its ID const key = `comment:${comment._id}`; + + // Generate key for indexing comments by postId const postID = `post_comments:${comment.postId}`; - // Set the comment in the cache + + // Store comment data as JSON string in Redis pipeline.set(key, JSON.stringify(comment)); - // Index comment on its postId + + // Index comment based on its postId pipeline.hset(postID, key, "null"); - // SET the time to live for each of the organization in the cache to 300s. + + // Set TTL for each comment and its postId index to 300 seconds (5 minutes) pipeline.expire(key, 300); pipeline.expire(postID, 300); } }); - // Execute the pipeline + // Execute the pipeline for batch Redis operations await pipeline.exec(); } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/CommentCache/deleteCommentFromCache.ts b/src/services/CommentCache/deleteCommentFromCache.ts index 30f8914e6d..70b826270d 100644 --- a/src/services/CommentCache/deleteCommentFromCache.ts +++ b/src/services/CommentCache/deleteCommentFromCache.ts @@ -1,10 +1,18 @@ import CommentCache from "../redisCache"; import type { InterfaceComment } from "../../models"; +/** + * Deletes the specified comment from Redis cache. + * + * @param comment - The InterfaceComment object representing the comment to delete. + * @returns A promise resolving to void. + */ export async function deleteCommentFromCache( comment: InterfaceComment, ): Promise { + // Generate the cache key for the comment based on its _id const key = `comment:${comment._id}`; + // Delete the comment from Redis cache await CommentCache.del(key); } diff --git a/src/services/EventCache/cacheEvents.ts b/src/services/EventCache/cacheEvents.ts index a73f300cb1..a07b9e27d3 100644 --- a/src/services/EventCache/cacheEvents.ts +++ b/src/services/EventCache/cacheEvents.ts @@ -2,23 +2,33 @@ import { logger } from "../../libraries"; import type { InterfaceEvent } from "../../models"; import EventCache from "../redisCache"; -// Function to store events in the cache using pipelining +/** + * Stores events in Redis cache with a specified time-to-live (TTL). + * @param events - Array of events to be cached. + * @returns Promise + */ export async function cacheEvents(events: InterfaceEvent[]): Promise { try { + // Create a pipeline for efficient Redis operations const pipeline = EventCache.pipeline(); events.forEach((event) => { if (event !== null) { + // Generate key for each event based on its ID const key = `event:${event._id}`; + + // Store event data as JSON string in Redis pipeline.set(key, JSON.stringify(event)); - // SET the time to live for each of the organization in the cache to 300s. + + // Set TTL for each event to 300 seconds (5 minutes) pipeline.expire(key, 300); } }); - // Execute the pipeline + // Execute the pipeline for batch Redis operations await pipeline.exec(); } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/EventCache/deleteEventFromCache.ts b/src/services/EventCache/deleteEventFromCache.ts index 0f36da3cce..f3e283a7a9 100644 --- a/src/services/EventCache/deleteEventFromCache.ts +++ b/src/services/EventCache/deleteEventFromCache.ts @@ -1,10 +1,18 @@ import EventCache from "../redisCache"; import type { Types } from "mongoose"; +/** + * Deletes the specified event from Redis cache. + * + * @param eventId - The ObjectId representing the event to delete from cache. + * @returns A promise resolving to void. + */ export async function deleteEventFromCache( eventId: Types.ObjectId, ): Promise { + // Generate the cache key for the event based on its eventId const key = `event:${eventId}`; + // Delete the event from Redis cache await EventCache.del(key); } diff --git a/src/services/OrganizationCache/cacheOrganizations.ts b/src/services/OrganizationCache/cacheOrganizations.ts index 6620ce04cf..6614e98f92 100644 --- a/src/services/OrganizationCache/cacheOrganizations.ts +++ b/src/services/OrganizationCache/cacheOrganizations.ts @@ -2,25 +2,35 @@ import { logger } from "../../libraries"; import type { InterfaceOrganization } from "../../models"; import OrganizationCache from "../redisCache"; -// Function to store organizations in the cache using pipelining +/** + * Stores organizations in Redis cache with a specified time-to-live (TTL). + * @param organizations - Array of organizations to be cached. + * @returns Promise + */ export async function cacheOrganizations( organizations: InterfaceOrganization[], ): Promise { try { + // Create a pipeline for efficient Redis operations const pipeline = OrganizationCache.pipeline(); organizations.forEach((org) => { if (org !== null) { + // Generate key for each organization based on its ID const key = `organization:${org._id}`; + + // Store organization data as JSON string in Redis pipeline.set(key, JSON.stringify(org)); - // SET the time to live for each of the organization in the cache to 300s. + + // Set TTL for each organization to 300 seconds (5 minutes) pipeline.expire(key, 300); } }); - // Execute the pipeline + // Execute the pipeline for batch Redis operations await pipeline.exec(); } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/OrganizationCache/deleteOrganizationFromCache.ts b/src/services/OrganizationCache/deleteOrganizationFromCache.ts index 030b13c64f..f760a778e0 100644 --- a/src/services/OrganizationCache/deleteOrganizationFromCache.ts +++ b/src/services/OrganizationCache/deleteOrganizationFromCache.ts @@ -1,10 +1,18 @@ import OrganizationCache from "../redisCache"; import type { InterfaceOrganization } from "../../models"; +/** + * Deletes the specified organization from Redis cache. + * + * @param organization - The InterfaceOrganization object representing the organization to delete. + * @returns A promise resolving to void. + */ export async function deleteOrganizationFromCache( organization: InterfaceOrganization, ): Promise { + // Generate the cache key for the organization based on its _id const key = `organization:${organization._id}`; + // Delete the organization from Redis cache await OrganizationCache.del(key); } diff --git a/src/services/PostCache/cachePosts.ts b/src/services/PostCache/cachePosts.ts index 91a5d123bd..0a703a1912 100644 --- a/src/services/PostCache/cachePosts.ts +++ b/src/services/PostCache/cachePosts.ts @@ -2,23 +2,35 @@ import { logger } from "../../libraries"; import type { InterfacePost } from "../../models"; import PostCache from "../redisCache"; -// Function to store posts in the cache using pipelining +/** + * Caches the provided array of InterfacePost objects in Redis. + * + * @param posts - An array of InterfacePost objects to be cached. + * @returns A promise resolving to void. + */ export async function cachePosts(posts: InterfacePost[]): Promise { try { + // Create a Redis pipeline for efficient multi-command execution const pipeline = PostCache.pipeline(); + // Iterate through each post in the array posts.forEach((post) => { if (post !== null) { + // Generate the cache key for each post const key = `post:${post._id}`; + + // Store the post object as a JSON string in Redis pipeline.set(key, JSON.stringify(post)); - // SET the time to live for each of the organization in the cache to 300s. - pipeline.expire(key, 300); + + // Set an expiration time for the cache key (in seconds) + pipeline.expire(key, 300); // 300 seconds (5 minutes) expiration } }); - // Execute the pipeline + // Execute the Redis pipeline await pipeline.exec(); } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/PostCache/deletePostFromCache.ts b/src/services/PostCache/deletePostFromCache.ts index 1a3ffa2dd6..aa0d80a9cf 100644 --- a/src/services/PostCache/deletePostFromCache.ts +++ b/src/services/PostCache/deletePostFromCache.ts @@ -1,7 +1,14 @@ import PostCache from "../redisCache"; +/** + * Deletes a post from Redis cache based on its postId. + * @param postId - The unique identifier of the post to delete. + * @returns Promise + */ export async function deletePostFromCache(postId: string): Promise { + // Construct the cache key for the specified postId const key = `post:${postId}`; + // Delete the post from Redis cache await PostCache.del(key); } diff --git a/src/services/UserCache/cacheUser.ts b/src/services/UserCache/cacheUser.ts index 0df2068e19..142420aa9e 100644 --- a/src/services/UserCache/cacheUser.ts +++ b/src/services/UserCache/cacheUser.ts @@ -2,18 +2,35 @@ import { logger } from "../../libraries"; import type { InterfaceUser } from "../../models"; import UserCache from "../redisCache"; +/** + * Caches the provided array of InterfaceUser objects in Redis. + * + * @param users - An array of InterfaceUser objects to be cached. + * @returns A promise resolving to void. + */ export async function cacheUsers(users: InterfaceUser[]): Promise { try { + // Create a Redis pipeline for efficient multi-command execution const pipeline = UserCache.pipeline(); + + // Iterate through each user in the array users.forEach((user) => { if (user !== null) { + // Generate the cache key for each user const key = `user:${user._id}`; + + // Store the user object as a JSON string in Redis pipeline.set(key, JSON.stringify(user)); - pipeline.expire(key, 300); + + // Set an expiration time for the cache key (in seconds) + pipeline.expire(key, 300); // 300 seconds (5 minutes) expiration } }); + + // Execute the Redis pipeline await pipeline.exec(); } catch (error) { + // Log any errors that occur during caching logger.info(error); } } diff --git a/src/services/UserCache/deleteUserFromCache.ts b/src/services/UserCache/deleteUserFromCache.ts index 70df4930c0..f451fd759d 100644 --- a/src/services/UserCache/deleteUserFromCache.ts +++ b/src/services/UserCache/deleteUserFromCache.ts @@ -1,6 +1,9 @@ import UserCache from "../redisCache"; + export async function deleteUserFromCache(userId: string): Promise { + // Construct the key for the user in the cache const key = `user:${userId}`; + // Delete the user entry from the Redis cache await UserCache.del(key); } diff --git a/src/services/UserCache/findUserInCache.ts b/src/services/UserCache/findUserInCache.ts index 19e8077c77..39a95438cb 100644 --- a/src/services/UserCache/findUserInCache.ts +++ b/src/services/UserCache/findUserInCache.ts @@ -3,22 +3,37 @@ import { logger } from "../../libraries"; import type { InterfaceUser } from "../../models"; import UserCache from "../redisCache"; +/** + * Retrieves user data from cache based on provided IDs. + * + * @param ids - An array of user IDs to retrieve from cache. + * @returns A promise resolving to an array of InterfaceUser objects or null if not found in cache. + */ export async function findUserInCache( ids: string[], ): Promise<(InterfaceUser | null)[]> { + // If no IDs are provided, return an array with null if (ids.length === 0) { return [null]; } + + // Generate cache keys for each ID const keys: string[] = ids.map((id) => { return `user:${id}`; }); + + // Retrieve user data from cache const userFoundInCache = await UserCache.mget(keys); + + // Parse cached JSON data into InterfaceUser objects const users = userFoundInCache.map((user: string | null) => { if (user === null) { return null; } try { const parsedUser = JSON.parse(user); + + // Convert specific fields to Types.ObjectId for Mongoose compatibility return { ...parsedUser, _id: new Types.ObjectId(parsedUser._id), @@ -49,8 +64,10 @@ export async function findUserInCache( : [], }; } catch (error) { + // Log error if parsing fails logger.info(`Error parsing user from cache: ${error}`); } }); + return users; } diff --git a/src/services/redisCache.ts b/src/services/redisCache.ts index b87469b859..8b8ad4a7f0 100644 --- a/src/services/redisCache.ts +++ b/src/services/redisCache.ts @@ -1,16 +1,19 @@ import { Redis } from "ioredis"; import { REDIS_HOST, REDIS_PASSWORD, REDIS_PORT } from "../constants"; +// Create a new Redis instance const RedisCache = new Redis({ host: REDIS_HOST, port: REDIS_PORT || 6379, password: REDIS_PASSWORD, }); -// Setting the limit of the max memory of the cache to 100MB +// Configure Redis settings if no password is set if (!REDIS_PASSWORD) { + // Set the maximum memory limit of the cache to 100MB RedisCache.config("SET", "maxmemory", 100 * 1024 * 1024); - // Setting the eviction policy to Least Frequently Used ,evicted first algorithm + + // Set the eviction policy to "Least Frequently Used, evicted first" algorithm RedisCache.config("SET", "maxmemory-policy", "allkeys-lfu"); } diff --git a/src/typeDefs/directives.ts b/src/typeDefs/directives.ts index d6f8c3cf15..e1f28c4a72 100644 --- a/src/typeDefs/directives.ts +++ b/src/typeDefs/directives.ts @@ -1,6 +1,11 @@ import { gql } from "graphql-tag"; // Place fields alphabetically to ensure easier lookup and navigation. + +/** + * GraphQL schema definition for directives. + */ + export const directives = gql` directive @auth on FIELD_DEFINITION diff --git a/src/typeDefs/enums.ts b/src/typeDefs/enums.ts index 37534ccf6e..a07f8c8741 100644 --- a/src/typeDefs/enums.ts +++ b/src/typeDefs/enums.ts @@ -116,6 +116,29 @@ export const enums = gql` capacity_DESC } + enum FundOrderByInput { + createdAt_ASC + createdAt_DESC + } + + enum CampaignOrderByInput { + startDate_ASC + startDate_DESC + endDate_ASC + endDate_DESC + fundingGoal_ASC + fundingGoal_DESC + } + + enum PledgeOrderByInput { + amount_ASC + amount_DESC + startDate_ASC + startDate_DESC + endDate_ASC + endDate_DESC + } + enum WeekDays { MONDAY TUESDAY diff --git a/src/typeDefs/errors/common.ts b/src/typeDefs/errors/common.ts index d6e81f986d..c76d90d392 100644 --- a/src/typeDefs/errors/common.ts +++ b/src/typeDefs/errors/common.ts @@ -1,5 +1,8 @@ import { gql } from "graphql-tag"; +/** + * GraphQL schema definition for common error types. + */ export const commonErrors = gql` interface Error { message: String! diff --git a/src/typeDefs/errors/connectionError.ts b/src/typeDefs/errors/connectionError.ts index ab496b189d..d865acdb3b 100644 --- a/src/typeDefs/errors/connectionError.ts +++ b/src/typeDefs/errors/connectionError.ts @@ -1,5 +1,8 @@ import { gql } from "graphql-tag"; +/** + * GraphQL schema definition for connection-related errors. + */ export const connectionError = gql` union ConnectionError = InvalidCursor | MaximumValueError diff --git a/src/typeDefs/errors/createAdminErrors.ts b/src/typeDefs/errors/createAdminErrors.ts index 7cac5a774d..d50bb898c3 100644 --- a/src/typeDefs/errors/createAdminErrors.ts +++ b/src/typeDefs/errors/createAdminErrors.ts @@ -1,5 +1,8 @@ import { gql } from "graphql-tag"; +/** + * GraphQL schema definition for errors related to creating an admin. + */ export const createAdminErrors = gql` type OrganizationNotFoundError implements Error { message: String! diff --git a/src/typeDefs/errors/createCommentErrors.ts b/src/typeDefs/errors/createCommentErrors.ts index 0a38ea4d32..383d1350c7 100644 --- a/src/typeDefs/errors/createCommentErrors.ts +++ b/src/typeDefs/errors/createCommentErrors.ts @@ -1,5 +1,9 @@ import { gql } from "graphql-tag"; +/** + * GraphQL schema definition for errors related to creating a comment. + */ + export const createCommentErrors = gql` type PostNotFoundError implements Error { message: String! diff --git a/src/typeDefs/errors/createDirectChatError.ts b/src/typeDefs/errors/createDirectChatError.ts index 72055321e2..f2a9b9eb43 100644 --- a/src/typeDefs/errors/createDirectChatError.ts +++ b/src/typeDefs/errors/createDirectChatError.ts @@ -1,4 +1,8 @@ import { gql } from "graphql-tag"; + +/** + * GraphQL schema definition for errors related to creating a direct chat. + */ export const createDirectChatErrors = gql` type OrganizationNotFoundError implements Error { message: String! diff --git a/src/typeDefs/errors/createMemberErrors.ts b/src/typeDefs/errors/createMemberErrors.ts index f291b0cbed..4786ca6839 100644 --- a/src/typeDefs/errors/createMemberErrors.ts +++ b/src/typeDefs/errors/createMemberErrors.ts @@ -1,5 +1,9 @@ import { gql } from "graphql-tag"; +/** + * GraphQL schema definition for errors related to creating a member. + */ + export const createMemberErrors = gql` type UserNotFoundError implements Error { message: String! diff --git a/src/typeDefs/errors/index.ts b/src/typeDefs/errors/index.ts index 9f51a5ec54..a71db2d6e3 100644 --- a/src/typeDefs/errors/index.ts +++ b/src/typeDefs/errors/index.ts @@ -4,6 +4,10 @@ import { createMemberErrors } from "./createMemberErrors"; import { createAdminErrors } from "./createAdminErrors"; import { createCommentErrors } from "./createCommentErrors"; import { createDirectChatErrors } from "./createDirectChatError"; + +/** + * Array of all error definitions. + */ export const errors = [ commonErrors, connectionError, diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index 7d7d365538..e209e9a09d 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -18,7 +18,7 @@ export const inputs = gql` input createChatInput { userIds: [ID!]! - organizationId: ID! + organizationId: ID } input createGroupChatInput { @@ -34,7 +34,7 @@ export const inputs = gql` input CreateUserTagInput { name: String! - tagColor: String! + tagColor: String parentTagId: ID organizationId: ID! } @@ -65,7 +65,6 @@ export const inputs = gql` duration: String attachments: [String] relatedEvent: ID - updatedBy: ID! urls: [String] users: [ID] categories: [ID] @@ -237,6 +236,10 @@ export const inputs = gql` name_contains: String } + input CampaignWhereInput { + name_contains: String + } + input LanguageInput { en_value: String! translation_lang_code: String! @@ -445,6 +448,7 @@ export const inputs = gql` currency: Currency } input UpdateFundCampaignPledgeInput { + users: [ID] startDate: Date endDate: Date amount: Float diff --git a/src/typeDefs/queries.ts b/src/typeDefs/queries.ts index f5fe8cf49e..80f0c4acaa 100644 --- a/src/typeDefs/queries.ts +++ b/src/typeDefs/queries.ts @@ -19,6 +19,10 @@ export const queries = gql` organizationId: ID! ): [ActionItemCategory] + agendaItemByEvent(relatedEventId: ID!): [AgendaItem] + + agendaItemByOrganization(organizationId: ID!): [AgendaItem] + agendaItemCategoriesByOrganization(organizationId: ID!): [AgendaCategory] getAgendaItem(id: ID!): AgendaItem @@ -39,6 +43,12 @@ export const queries = gql` directChatsByUserID(id: ID!): [DirectChat] + directChatById(id: ID!): DirectChat + + groupChatById(id: ID!): GroupChat + + groupChatsByUserId(id: ID!): [GroupChat] + directChatsMessagesByChatID(id: ID!): [DirectChatMessage] event(id: ID!): Event @@ -54,7 +64,11 @@ export const queries = gql` eventVolunteersByEvent(id: ID!): [EventVolunteer] - fundsByOrganization(organizationId: ID!, where: FundWhereInput): [Fund] + fundsByOrganization( + organizationId: ID! + where: FundWhereInput + orderBy: FundOrderByInput + ): [Fund] getDonationById(id: ID!): Donation! @@ -63,8 +77,15 @@ export const queries = gql` getEventAttendee(userId: ID!, eventId: ID!): EventAttendee getEventInvitesByUserId(userId: ID!): [EventAttendee!]! - getFundById(id: ID!): Fund! - getFundraisingCampaignById(id: ID!): FundraisingCampaign! + getFundById( + id: ID! + orderBy: CampaignOrderByInput + where: CampaignWhereInput + ): Fund! + getFundraisingCampaignById( + id: ID! + orderBy: PledgeOrderByInput + ): FundraisingCampaign! getFundraisingCampaignPledgeById(id: ID!): FundraisingCampaignPledge! getDonationByOrgId(orgId: ID!): [Donation] diff --git a/src/typeDefs/subscriptions.ts b/src/typeDefs/subscriptions.ts index f7c1dff8c1..4c718b9761 100644 --- a/src/typeDefs/subscriptions.ts +++ b/src/typeDefs/subscriptions.ts @@ -4,8 +4,8 @@ import { gql } from "graphql-tag"; export const subscriptions = gql` type Subscription { directMessageChat: MessageChat - messageSentToDirectChat: DirectChatMessage - messageSentToGroupChat: GroupChatMessage + messageSentToDirectChat(userId: ID!): DirectChatMessage + messageSentToGroupChat(userId: ID!): GroupChatMessage onPluginUpdate: Plugin } `; diff --git a/src/typeDefs/types.ts b/src/typeDefs/types.ts index 10afcbfccf..a3a1b41ffa 100644 --- a/src/typeDefs/types.ts +++ b/src/typeDefs/types.ts @@ -183,7 +183,7 @@ export const types = gql` creator: User createdAt: DateTime! updatedAt: DateTime! - organization: Organization! + organization: Organization } type DirectChatMessage { @@ -334,7 +334,7 @@ export const types = gql` isDefault: Boolean! isArchived: Boolean! creator: User - campaigns: [FundraisingCampaign!] + campaigns: [FundraisingCampaign] createdAt: DateTime! updatedAt: DateTime! } @@ -372,6 +372,7 @@ export const types = gql` type GroupChat { _id: ID! + title: String! users: [User!]! messages: [GroupChatMessage] creator: User @@ -738,6 +739,7 @@ export const types = gql` type UserTagsConnection { edges: [UserTagsConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! + totalCount: PositiveInt } """ diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index eb366ab924..4c05807c81 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -238,6 +238,18 @@ export type AuthData = { user: User; }; +export type CampaignOrderByInput = + | 'endDate_ASC' + | 'endDate_DESC' + | 'fundingGoal_ASC' + | 'fundingGoal_DESC' + | 'startDate_ASC' + | 'startDate_DESC'; + +export type CampaignWhereInput = { + name_contains?: InputMaybe; +}; + export type CheckIn = { __typename?: 'CheckIn'; _id: Scalars['ID']['output']; @@ -402,7 +414,7 @@ export type CreateUserTagInput = { name: Scalars['String']['input']; organizationId: Scalars['ID']['input']; parentTagId?: InputMaybe; - tagColor: Scalars['String']['input']; + tagColor?: InputMaybe; }; export type Currency = @@ -604,7 +616,7 @@ export type DirectChat = { createdAt: Scalars['DateTime']['output']; creator?: Maybe; messages?: Maybe>>; - organization: Organization; + organization?: Maybe; updatedAt: Scalars['DateTime']['output']; users: Array; }; @@ -894,7 +906,7 @@ export type Frequency = export type Fund = { __typename?: 'Fund'; _id: Scalars['ID']['output']; - campaigns?: Maybe>; + campaigns?: Maybe>>; createdAt: Scalars['DateTime']['output']; creator?: Maybe; isArchived: Scalars['Boolean']['output']; @@ -933,6 +945,10 @@ export type FundInput = { taxDeductible: Scalars['Boolean']['input']; }; +export type FundOrderByInput = + | 'createdAt_ASC' + | 'createdAt_DESC'; + export type FundWhereInput = { name_contains?: InputMaybe; }; @@ -985,6 +1001,7 @@ export type GroupChat = { creator?: Maybe; messages?: Maybe>>; organization: Organization; + title: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; users: Array; }; @@ -2099,6 +2116,14 @@ export type PaginationDirection = | 'BACKWARD' | 'FORWARD'; +export type PledgeOrderByInput = + | 'amount_ASC' + | 'amount_DESC' + | 'endDate_ASC' + | 'endDate_DESC' + | 'startDate_ASC' + | 'startDate_DESC'; + export type Plugin = { __typename?: 'Plugin'; _id: Scalars['ID']['output']; @@ -2229,10 +2254,13 @@ export type Query = { adminPlugin?: Maybe>>; advertisementsConnection?: Maybe; agendaCategory: AgendaCategory; + agendaItemByEvent?: Maybe>>; + agendaItemByOrganization?: Maybe>>; agendaItemCategoriesByOrganization?: Maybe>>; checkAuth: User; customDataByOrganization: Array; customFieldsByOrganization?: Maybe>>; + directChatById?: Maybe; directChatsByUserID?: Maybe>>; directChatsMessagesByChatID?: Maybe>>; event?: Maybe; @@ -2258,6 +2286,8 @@ export type Query = { getPlugins?: Maybe>>; getVenueByOrgId?: Maybe>>; getlanguage?: Maybe>>; + groupChatById?: Maybe; + groupChatsByUserId?: Maybe>>; hasSubmittedFeedback?: Maybe; isSampleOrganization: Scalars['Boolean']['output']; joinedOrganizations?: Maybe>>; @@ -2313,6 +2343,16 @@ export type QueryAgendaCategoryArgs = { }; +export type QueryAgendaItemByEventArgs = { + relatedEventId: Scalars['ID']['input']; +}; + + +export type QueryAgendaItemByOrganizationArgs = { + organizationId: Scalars['ID']['input']; +}; + + export type QueryAgendaItemCategoriesByOrganizationArgs = { organizationId: Scalars['ID']['input']; }; @@ -2328,6 +2368,11 @@ export type QueryCustomFieldsByOrganizationArgs = { }; +export type QueryDirectChatByIdArgs = { + id: Scalars['ID']['input']; +}; + + export type QueryDirectChatsByUserIdArgs = { id: Scalars['ID']['input']; }; @@ -2363,6 +2408,7 @@ export type QueryEventsByOrganizationConnectionArgs = { export type QueryFundsByOrganizationArgs = { + orderBy?: InputMaybe; organizationId: Scalars['ID']['input']; where?: InputMaybe; }; @@ -2419,11 +2465,14 @@ export type QueryGetEventInvitesByUserIdArgs = { export type QueryGetFundByIdArgs = { id: Scalars['ID']['input']; + orderBy?: InputMaybe; + where?: InputMaybe; }; export type QueryGetFundraisingCampaignByIdArgs = { id: Scalars['ID']['input']; + orderBy?: InputMaybe; }; @@ -2451,6 +2500,16 @@ export type QueryGetlanguageArgs = { }; +export type QueryGroupChatByIdArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryGroupChatsByUserIdArgs = { + id: Scalars['ID']['input']; +}; + + export type QueryHasSubmittedFeedbackArgs = { eventId: Scalars['ID']['input']; userId: Scalars['ID']['input']; @@ -2630,6 +2689,16 @@ export type Subscription = { onPluginUpdate?: Maybe; }; + +export type SubscriptionMessageSentToDirectChatArgs = { + userId: Scalars['ID']['input']; +}; + + +export type SubscriptionMessageSentToGroupChatArgs = { + userId: Scalars['ID']['input']; +}; + export type ToggleUserTagAssignInput = { tagId: Scalars['ID']['input']; userId: Scalars['ID']['input']; @@ -2698,7 +2767,6 @@ export type UpdateAgendaItemInput = { relatedEvent?: InputMaybe; sequence?: InputMaybe; title?: InputMaybe; - updatedBy: Scalars['ID']['input']; urls?: InputMaybe>>; users?: InputMaybe>>; }; @@ -2760,6 +2828,7 @@ export type UpdateFundCampaignPledgeInput = { currency?: InputMaybe; endDate?: InputMaybe; startDate?: InputMaybe; + users?: InputMaybe>>; }; export type UpdateFundInput = { @@ -2980,6 +3049,7 @@ export type UserTagsConnection = { __typename?: 'UserTagsConnection'; edges: Array; pageInfo: DefaultConnectionPageInfo; + totalCount?: Maybe; }; /** A default connection edge on the UserTag type for UserTagsConnection. */ @@ -3076,7 +3146,7 @@ export type WeekDays = | 'WEDNESDAY'; export type CreateChatInput = { - organizationId: Scalars['ID']['input']; + organizationId?: InputMaybe; userIds: Array; }; @@ -3161,7 +3231,7 @@ export type DirectiveResolverFn TResult | Promise; /** Mapping of union types */ -export type ResolversUnionTypes> = { +export type ResolversUnionTypes<_RefType extends Record> = { ConnectionError: ( InvalidCursor ) | ( MaximumValueError ); CreateAdminError: ( OrganizationMemberNotFoundError ) | ( OrganizationNotFoundError ) | ( UserNotAuthorizedError ) | ( UserNotFoundError ); CreateCommentError: ( PostNotFoundError ); @@ -3170,7 +3240,7 @@ export type ResolversUnionTypes> = { }; /** Mapping of interface types */ -export type ResolversInterfaceTypes> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { ConnectionPageInfo: ( DefaultConnectionPageInfo ); Error: ( MemberNotFoundError ) | ( OrganizationMemberNotFoundError ) | ( OrganizationNotFoundError ) | ( PostNotFoundError ) | ( UnauthenticatedError ) | ( UnauthorizedError ) | ( UserNotAuthorizedAdminError ) | ( UserNotAuthorizedError ) | ( UserNotFoundError ); FieldError: ( InvalidCursor ) | ( MaximumLengthError ) | ( MaximumValueError ) | ( MinimumLengthError ) | ( MinimumValueError ); @@ -3197,6 +3267,8 @@ export type ResolversTypes = { AppUserProfile: ResolverTypeWrapper; AuthData: ResolverTypeWrapper & { appUserProfile: ResolversTypes['AppUserProfile'], user: ResolversTypes['User'] }>; Boolean: ResolverTypeWrapper; + CampaignOrderByInput: CampaignOrderByInput; + CampaignWhereInput: CampaignWhereInput; CheckIn: ResolverTypeWrapper; CheckInCheckOutInput: CheckInCheckOutInput; CheckInStatus: ResolverTypeWrapper & { checkIn?: Maybe, user: ResolversTypes['User'] }>; @@ -3259,6 +3331,7 @@ export type ResolversTypes = { FundCampaignInput: FundCampaignInput; FundCampaignPledgeInput: FundCampaignPledgeInput; FundInput: FundInput; + FundOrderByInput: FundOrderByInput; FundWhereInput: FundWhereInput; FundraisingCampaign: ResolverTypeWrapper; FundraisingCampaignPledge: ResolverTypeWrapper; @@ -3304,6 +3377,7 @@ export type ResolversTypes = { PageInfo: ResolverTypeWrapper; PaginationDirection: PaginationDirection; PhoneNumber: ResolverTypeWrapper; + PledgeOrderByInput: PledgeOrderByInput; Plugin: ResolverTypeWrapper; PluginField: ResolverTypeWrapper; PluginFieldInput: PluginFieldInput; @@ -3405,6 +3479,7 @@ export type ResolversParentTypes = { AppUserProfile: InterfaceAppUserProfileModel; AuthData: Omit & { appUserProfile: ResolversParentTypes['AppUserProfile'], user: ResolversParentTypes['User'] }; Boolean: Scalars['Boolean']['output']; + CampaignWhereInput: CampaignWhereInput; CheckIn: InterfaceCheckInModel; CheckInCheckOutInput: CheckInCheckOutInput; CheckInStatus: Omit & { checkIn?: Maybe, user: ResolversParentTypes['User'] }; @@ -3865,7 +3940,7 @@ export type DirectChatResolvers; creator?: Resolver, ParentType, ContextType>; messages?: Resolver>>, ParentType, ContextType>; - organization?: Resolver; + organization?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; users?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -4003,7 +4078,7 @@ export type FieldErrorResolvers = { _id?: Resolver; - campaigns?: Resolver>, ParentType, ContextType>; + campaigns?: Resolver>>, ParentType, ContextType>; createdAt?: Resolver; creator?: Resolver, ParentType, ContextType>; isArchived?: Resolver; @@ -4058,6 +4133,7 @@ export type GroupChatResolvers, ParentType, ContextType>; messages?: Resolver>>, ParentType, ContextType>; organization?: Resolver; + title?: Resolver; updatedAt?: Resolver; users?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -4450,10 +4526,13 @@ export type QueryResolvers>>, ParentType, ContextType, RequireFields>; advertisementsConnection?: Resolver, ParentType, ContextType, Partial>; agendaCategory?: Resolver>; + agendaItemByEvent?: Resolver>>, ParentType, ContextType, RequireFields>; + agendaItemByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; agendaItemCategoriesByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; checkAuth?: Resolver; customDataByOrganization?: Resolver, ParentType, ContextType, RequireFields>; customFieldsByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; + directChatById?: Resolver, ParentType, ContextType, RequireFields>; directChatsByUserID?: Resolver>>, ParentType, ContextType, RequireFields>; directChatsMessagesByChatID?: Resolver>>, ParentType, ContextType, RequireFields>; event?: Resolver, ParentType, ContextType, RequireFields>; @@ -4479,6 +4558,8 @@ export type QueryResolvers>>, ParentType, ContextType>; getVenueByOrgId?: Resolver>>, ParentType, ContextType, RequireFields>; getlanguage?: Resolver>>, ParentType, ContextType, RequireFields>; + groupChatById?: Resolver, ParentType, ContextType, RequireFields>; + groupChatsByUserId?: Resolver>>, ParentType, ContextType, RequireFields>; hasSubmittedFeedback?: Resolver, ParentType, ContextType, RequireFields>; isSampleOrganization?: Resolver>; joinedOrganizations?: Resolver>>, ParentType, ContextType, Partial>; @@ -4527,8 +4608,8 @@ export type SocialMediaUrlsResolvers = { directMessageChat?: SubscriptionResolver, "directMessageChat", ParentType, ContextType>; - messageSentToDirectChat?: SubscriptionResolver, "messageSentToDirectChat", ParentType, ContextType>; - messageSentToGroupChat?: SubscriptionResolver, "messageSentToGroupChat", ParentType, ContextType>; + messageSentToDirectChat?: SubscriptionResolver, "messageSentToDirectChat", ParentType, ContextType, RequireFields>; + messageSentToGroupChat?: SubscriptionResolver, "messageSentToGroupChat", ParentType, ContextType, RequireFields>; onPluginUpdate?: SubscriptionResolver, "onPluginUpdate", ParentType, ContextType>; }; @@ -4659,6 +4740,7 @@ export type UserTagResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; + totalCount?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/src/utilities/PII/decryption.ts b/src/utilities/PII/decryption.ts index d93b505ec8..4521fbae8a 100644 --- a/src/utilities/PII/decryption.ts +++ b/src/utilities/PII/decryption.ts @@ -1,17 +1,29 @@ import crypto from "crypto"; -// Decryption function +/** + * Decrypts the given encrypted text using AES-256-CBC decryption. + * + * @param encryptedText - The encrypted text to decrypt, encoded as a hexadecimal string. + * @param key - The encryption key used for decryption. + * @param iv - The initialization vector (IV), used to ensure different ciphertexts encrypt to different ciphertexts even if the plaintexts are identical. + * @returns The decrypted plaintext string. + */ export function decrypt( encryptedText: string, key: string, iv: string, ): string { + // Create a decipher object with AES-256-CBC algorithm, using the provided key and IV const decipher = crypto.createDecipheriv( "aes-256-cbc", - Buffer.from(key), - Buffer.from(iv, "hex"), + Buffer.from(key), // Convert key from string to buffer + Buffer.from(iv, "hex"), // Convert IV from hexadecimal string to buffer ); + + // Decrypt the encrypted text let decrypted = decipher.update(encryptedText, "hex", "utf8"); decrypted += decipher.final("utf8"); + + // Return the decrypted plaintext return decrypted; } diff --git a/src/utilities/PII/encryption.ts b/src/utilities/PII/encryption.ts index 05c85bd86a..26953657fa 100644 --- a/src/utilities/PII/encryption.ts +++ b/src/utilities/PII/encryption.ts @@ -1,13 +1,24 @@ import crypto from "crypto"; -// Encryption function +/** + * Encrypts plaintext using AES-256-CBC encryption. + * @param text - The plaintext to encrypt. + * @param key - The encryption key as a string. + * @param iv - The initialization vector (IV) as a string in hexadecimal format. + * @returns The encrypted ciphertext as a hexadecimal string. + */ export function encrypt(text: string, key: string, iv: string): string { + // Create a cipher object using AES-256-CBC algorithm with provided key and IV const cipher = crypto.createCipheriv( "aes-256-cbc", - Buffer.from(key), - Buffer.from(iv, "hex"), + Buffer.from(key), // Convert key string to buffer + Buffer.from(iv, "hex"), // Convert IV string from hexadecimal to buffer ); - let encrypted = cipher.update(text); - encrypted = Buffer.concat([encrypted, cipher.final()]); + + // Encrypt the plaintext + let encrypted = cipher.update(text); // Perform encryption + encrypted = Buffer.concat([encrypted, cipher.final()]); // Finalize encryption and concatenate + + // Return encrypted ciphertext as hexadecimal string return encrypted.toString("hex"); } diff --git a/src/utilities/PII/isAuthorised.ts b/src/utilities/PII/isAuthorised.ts index 65c4120de5..0fb2412fcd 100644 --- a/src/utilities/PII/isAuthorised.ts +++ b/src/utilities/PII/isAuthorised.ts @@ -1,11 +1,19 @@ import type { User } from "../../types/generatedGraphQLTypes"; +/** + * Checks if the requesting user is authorized to access or modify the requested user's data. + * @param requestingUser - The user making the request. + * @param requestedUser - The user whose data is being requested or modified. + * @returns `true` if the requesting user is authorized, `false` otherwise. + */ export function isAuthorised( requestingUser: User, requestedUser: User, ): boolean { + // Check if the requesting user is the same as the requested user if (requestedUser !== requestedUser) { - return false; + return false; // Not authorized if requesting user is not the same as requested user } - return true; + + return true; // Authorized if requesting user is the same as requested user } diff --git a/src/utilities/adminCheck.ts b/src/utilities/adminCheck.ts index 63df8d4b8f..3d49abcd83 100644 --- a/src/utilities/adminCheck.ts +++ b/src/utilities/adminCheck.ts @@ -4,27 +4,40 @@ import { USER_NOT_AUTHORIZED_ADMIN } from "../constants"; import { errors, requestContext } from "../libraries"; import type { InterfaceOrganization } from "../models"; import { AppUserProfile } from "../models"; + /** - * If the current user is an admin of the organisation, this function returns `true` otherwise it returns `false`. + * Checks if the current user is an admin of the organization. + * If the user is an admin, the function completes successfully. Otherwise, it throws an UnauthorizedError. * @remarks * This is a utility method. - * @param userId - Current user id. - * @param organization - Organization data of `InterfaceOrganization` type. + * @param userId - The ID of the current user. It can be a string or a Types.ObjectId. + * @param organization - The organization data of `InterfaceOrganization` type. * @returns `True` or `False`. */ export const adminCheck = async ( userId: string | Types.ObjectId, organization: InterfaceOrganization, ): Promise => { + /** + * Check if the user is listed as an admin in the organization. + * Compares the user ID with the admin IDs in the organization. + */ const userIsOrganizationAdmin = organization.admins.some( (admin) => admin === userId || new mongoose.Types.ObjectId(admin).toString() === userId.toString(), ); + /** + * Fetch the user's profile from the AppUserProfile collection. + */ const userAppProfile = await AppUserProfile.findOne({ userId, }).lean(); + + /** + * If the user's profile is not found, throw an UnauthorizedError. + */ if (!userAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ADMIN.MESSAGE), @@ -32,8 +45,15 @@ export const adminCheck = async ( USER_NOT_AUTHORIZED_ADMIN.PARAM, ); } + + /** + * Check if the user has super admin privileges. + */ const isUserSuperAdmin: boolean = userAppProfile.isSuperAdmin; + /** + * If the user is neither an organization admin nor a super admin, throw an UnauthorizedError. + */ if (!userIsOrganizationAdmin && !isUserSuperAdmin) { throw new errors.UnauthorizedError( requestContext.translate(`${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`), diff --git a/src/utilities/auth.ts b/src/utilities/auth.ts index 90335ddbaf..84389ac1f4 100644 --- a/src/utilities/auth.ts +++ b/src/utilities/auth.ts @@ -3,6 +3,9 @@ import { ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET } from "../constants"; import type { InterfaceAppUserProfile, InterfaceUser } from "../models"; import { User } from "../models"; +/** + * Interface representing the payload of a JWT token. + */ export interface InterfaceJwtTokenPayload { tokenVersion: number; userId: string; @@ -10,10 +13,13 @@ export interface InterfaceJwtTokenPayload { lastName: string; email: string; } + /** - * This function creates a json web token which expires in 15 minutes. - * It signs the given payload(user data) into a JSON Web Token string payload. + * Creates an access token (JWT) for a user that expires in 40 minutes. + * The token contains user data and is signed with the access token secret. + * * @param user - User data + * @param appUserProfile - Application user profile data * @returns JSON Web Token string payload */ export const createAccessToken = ( @@ -35,6 +41,14 @@ export const createAccessToken = ( ); }; +/** + * Creates a refresh token (JWT) for a user that expires in 30 days. + * The token contains user data and is signed with the refresh token secret. + * + * @param user - User data + * @param appUserProfile - Application user profile data + * @returns JSON Web Token string payload + */ export const createRefreshToken = ( user: InterfaceUser, appUserProfile: InterfaceAppUserProfile, @@ -54,6 +68,13 @@ export const createRefreshToken = ( ); }; +/** + * Revokes the refresh token for a user by removing the token from the user's profile. + * This function searches for the user by their ID and unsets the token field in the user's document. + * + * @param userId - The ID of the user whose refresh token is to be revoked + * @returns A promise that resolves when the token has been revoked + */ export const revokeRefreshToken = async (userId: string): Promise => { const user = await User.findOne({ _id: userId }).lean(); diff --git a/src/utilities/checkReplicaSet.ts b/src/utilities/checkReplicaSet.ts index 56f75deba8..61d76292b4 100644 --- a/src/utilities/checkReplicaSet.ts +++ b/src/utilities/checkReplicaSet.ts @@ -1,6 +1,13 @@ import mongoose from "mongoose"; import { logger } from "../libraries"; +/** + * Checks if the MongoDB connection is part of a replica set. + * This function sends a 'hello' command to the MongoDB admin database to retrieve server information, + * and determines if the connection is part of a replica set by checking for the presence of 'hosts' and 'setName' in the result. + * + * @returns A promise that resolves to a boolean indicating whether the connection is part of a replica set (true) or not (false). + */ export const checkReplicaSet = async (): Promise => { try { const adminDb = mongoose.connection.db.admin(); diff --git a/src/utilities/copyToClipboard.ts b/src/utilities/copyToClipboard.ts index d24dfafbc8..ba95a3ec67 100644 --- a/src/utilities/copyToClipboard.ts +++ b/src/utilities/copyToClipboard.ts @@ -1,13 +1,14 @@ import ncp from "copy-paste"; import { IN_PRODUCTION } from "../constants"; + /** - * This utility function copy the text into the clipboard (test change). + * Copies the given text to the clipboard. * @remarks - * This is a utility method. This works only in development or test mode. - * @param text - The content that need to be copied. + * This is a utility method and works only in development or test mode. + * @param text - The content that needs to be copied to the clipboard. */ export const copyToClipboard = (text: string): void => { - // Only copies in development or test mode + // Only copies text to the clipboard in development or test mode if (IN_PRODUCTION !== true) { ncp.copy(text, () => {}); } diff --git a/src/utilities/createSampleOrganizationUtil.ts b/src/utilities/createSampleOrganizationUtil.ts index f6523de23a..fcb51638ec 100644 --- a/src/utilities/createSampleOrganizationUtil.ts +++ b/src/utilities/createSampleOrganizationUtil.ts @@ -14,6 +14,13 @@ import { SampleData } from "../models/SampleData"; /* eslint-disable */ +/** + * Generates user data for a given organization and user type. + * + * @param organizationId - The ID of the organization the user belongs to + * @param userType - The type of the user ('ADMIN' or 'USER') + * @returns A promise that resolves to an object containing the created user and their application profile + */ export const generateUserData = async ( organizationId: string, userType: string, @@ -71,6 +78,13 @@ export const generateUserData = async ( }; }; +/** + * Generates event data for a given list of users and organization. + * + * @param users - The list of users associated with the event + * @param organizationId - The ID of the organization the event belongs to + * @returns A promise that resolves to the created event + */ export const generateEventData = async ( users: InterfaceUser[], organizationId: string, @@ -127,6 +141,13 @@ export const generateEventData = async ( return event; }; +/** + * Generates post data for a given list of users and organization. + * + * @param users - The list of users associated with the post + * @param organizationId - The ID of the organization the post belongs to + * @returns A promise that resolves to the created post + */ export const generatePostData = async ( users: InterfaceUser[], organizationId: string, @@ -157,6 +178,14 @@ export const generatePostData = async ( return post; }; +/** + * Creates multiple posts for a given list of users and organization. + * + * @param numPosts - The number of posts to create + * @param users - The list of users associated with the posts + * @param organizationId - The ID of the organization the posts belong to + * @returns A promise that resolves to an array of created posts + */ const createPosts = async ( numPosts: number, users: InterfaceUser[], @@ -170,6 +199,14 @@ const createPosts = async ( return posts; }; +/** + * Creates multiple events for a given list of users and organization. + * + * @param numEvents - The number of events to create + * @param users - The list of users associated with the events + * @param organizationId - The ID of the organization the events belong to + * @returns A promise that resolves to an array of created events + */ const createEvents = async ( numEvents: number, users: InterfaceUser[], @@ -184,6 +221,13 @@ const createEvents = async ( return events; }; +/** + * Generates random plugin data for a given number of plugins and list of users. + * + * @param numberOfPlugins - The number of plugins to create + * @param users - The list of users associated with the plugins + * @returns A promise that resolves to an array of promises for created plugins + */ export const generateRandomPlugins = async ( numberOfPlugins: number, users: string[], @@ -214,6 +258,11 @@ export const generateRandomPlugins = async ( return pluginPromises; }; +/** + * Creates a sample organization with associated users, events, posts, and plugins. + * + * @returns A promise that resolves when the sample organization and its related data have been created + */ export const createSampleOrganization = async (): Promise => { const _id = faker.database.mongodbObjectId(); const userData = await generateUserData(_id, "ADMIN"); diff --git a/src/utilities/dateValidator.ts b/src/utilities/dateValidator.ts index c61aa5d56d..3dff36c2b4 100644 --- a/src/utilities/dateValidator.ts +++ b/src/utilities/dateValidator.ts @@ -3,12 +3,20 @@ import { START_DATE_VALIDATION_ERROR, } from "../constants"; import { errors, requestContext } from "../libraries"; + /** - * This function validates the date. - * @param startDate - starting Date - * @param endDate - Ending Date + * Validates the start and end dates. + * @param startDate - The starting date. + * @param endDate - The ending date. */ -export const validateDate = (startDate: Date, endDate: Date): void => { +export const validateDate = ( + startDate: Date | undefined, + endDate: Date | undefined, +): void => { + /** + * Checks if the start date is provided and if it's in the past. + * Throws an InputValidationError if the start date is invalid. + */ if (startDate && new Date(startDate) < new Date(new Date().toDateString())) { throw new errors.InputValidationError( requestContext.translate(START_DATE_VALIDATION_ERROR.MESSAGE), @@ -17,8 +25,11 @@ export const validateDate = (startDate: Date, endDate: Date): void => { ); } - //Checks if the end date is valid - if (endDate && new Date(endDate) < new Date(startDate)) { + /** + * Checks if the end date is provided and if it's before the start date. + * Throws an InputValidationError if the end date is invalid. + */ + if (endDate && startDate && new Date(endDate) < new Date(startDate)) { throw new errors.InputValidationError( requestContext.translate(END_DATE_VALIDATION_ERROR.MESSAGE), END_DATE_VALIDATION_ERROR.CODE, diff --git a/src/utilities/deleteDuplicatedImage.ts b/src/utilities/deleteDuplicatedImage.ts index a6ff192aa6..0e44555977 100644 --- a/src/utilities/deleteDuplicatedImage.ts +++ b/src/utilities/deleteDuplicatedImage.ts @@ -1,17 +1,21 @@ import type { PathLike } from "fs"; import { unlink } from "fs"; import { logger } from "../libraries"; + /** - * This function deletes a duplicated image using the function fs.unlink(). - * @param imagePath - Path of the image + * Deletes a duplicated image file using fs.unlink(). + * @param imagePath - The path to the image file to delete. + * @throws Throws an error if deletion fails. */ export const deleteDuplicatedImage = (imagePath: PathLike): void => { + // Attempt to delete the image file unlink(imagePath, function (error) { if (error) { + // Throw an error if deletion fails throw error; } - // if no error is thrown, file has been deleted successfully + // Log a success message if deletion succeeds logger.info("File was deleted as it already exists in the db!"); }); }; diff --git a/src/utilities/deleteImage.ts b/src/utilities/deleteImage.ts index 74c30cba1f..e578617929 100644 --- a/src/utilities/deleteImage.ts +++ b/src/utilities/deleteImage.ts @@ -2,12 +2,13 @@ import { unlink } from "fs"; import { logger } from "../libraries"; import { ImageHash } from "../models"; import { reuploadDuplicateCheck } from "./reuploadDuplicateCheck"; + /** - * This function deletes an image if it is only used once. - * It is also ensured that the image hash isn't used by multiple users/organization before deleting it - * After deleting the image, the number of uses of the hashed image are decremented by one. - * @param imageToBeDeleted - Path of image - * @param imageBelongingToItem - Does image belong to an item + * Deletes an image file if it meets deletion criteria based on usage and duplicate checks. + * + * @param imageToBeDeleted - The path of the image file to be deleted + * @param imageBelongingToItem - Optional. Indicates if the image belongs to a specific item for duplicate check + * @returns A promise that resolves once the image is successfully deleted */ export const deleteImage = async ( imageToBeDeleted: string, @@ -16,6 +17,7 @@ export const deleteImage = async ( let imageIsDuplicate = false; if (imageBelongingToItem) { + // Check if the image is a duplicate of another image belonging to the same item imageIsDuplicate = await reuploadDuplicateCheck( imageToBeDeleted, imageBelongingToItem, @@ -23,20 +25,21 @@ export const deleteImage = async ( } if (!imageIsDuplicate) { - /* - Only remove the old image if its different from the new one - Ensure image hash isn't used by multiple users/organization before deleting it - */ + // Proceed with deletion only if the image is not a duplicate + + // Retrieve the image hash information from the database const imageHash = await ImageHash.findOne({ fileName: imageToBeDeleted, }).lean(); if (imageHash && imageHash?.numberOfUses > 1) { - // Image can only be deleted if imageHash.numberOfUses === 1 + // If the image is used by multiple users/organizations, log that it cannot be deleted logger.info("Image cannot be deleted"); } else { + // If the image is only used once or not tracked by image hash, proceed with deletion logger.info("Image is only used once and therefore can be deleted"); + // Delete the image file from the filesystem unlink(imageToBeDeleted, (error) => { if (error) { throw error; @@ -47,6 +50,7 @@ export const deleteImage = async ( }); } + // Decrease the usage count of the image hash in the database await ImageHash.updateOne( { fileName: imageToBeDeleted, diff --git a/src/utilities/encodedImageStorage/deletePreviousImage.ts b/src/utilities/encodedImageStorage/deletePreviousImage.ts index 78a35b1712..62eaba2b7a 100644 --- a/src/utilities/encodedImageStorage/deletePreviousImage.ts +++ b/src/utilities/encodedImageStorage/deletePreviousImage.ts @@ -2,15 +2,24 @@ import { unlink } from "fs/promises"; import path from "path"; import { EncodedImage } from "../../models/EncodedImage"; +/** + * Deletes the previous image file if its `numberOfUses` is 1 and updates the `numberOfUses` in the database. + * @param imageToBeDeletedPath - Path of the image to be deleted. + */ export const deletePreviousImage = async ( imageToBeDeletedPath: string, ): Promise => { + // Find the EncodedImage document with the given fileName const imageToBeDeleted = await EncodedImage.findOne({ fileName: imageToBeDeletedPath ?? "", }); + // Check if the image exists and its numberOfUses is 1 if (imageToBeDeleted?.numberOfUses === 1) { + // Delete the image file from the file system await unlink(path.join(__dirname, "../../../" + imageToBeDeleted.fileName)); + + // Delete the EncodedImage document from the database await EncodedImage.deleteOne({ fileName: imageToBeDeletedPath, }); diff --git a/src/utilities/encodedImageStorage/encodedImageExtensionCheck.ts b/src/utilities/encodedImageStorage/encodedImageExtensionCheck.ts index bc6256012e..582f184ebb 100644 --- a/src/utilities/encodedImageStorage/encodedImageExtensionCheck.ts +++ b/src/utilities/encodedImageStorage/encodedImageExtensionCheck.ts @@ -1,4 +1,10 @@ +/** + * Checks if the extension of an encoded image URL is valid (png, jpg, jpeg). + * @param encodedUrl - Encoded URL of the image. + * @returns `true` if the extension is valid, otherwise `false`. + */ export const encodedImageExtentionCheck = (encodedUrl: string): boolean => { + // Extract the extension from the encodedUrl const extension = encodedUrl.substring( "data:".length, encodedUrl.indexOf(";base64"), diff --git a/src/utilities/encodedImageStorage/uploadEncodedImage.ts b/src/utilities/encodedImageStorage/uploadEncodedImage.ts index 919bc8e5ad..45fcaad12b 100644 --- a/src/utilities/encodedImageStorage/uploadEncodedImage.ts +++ b/src/utilities/encodedImageStorage/uploadEncodedImage.ts @@ -8,10 +8,22 @@ import { EncodedImage } from "../../models/EncodedImage"; import path from "path"; import { deletePreviousImage } from "./deletePreviousImage"; +/** + * Checks if the size of the base64 encoded image data is within the allowable limit. + * + * @param size - The size of the image data in kilobytes. + * @returns `true` if the size is within the limit, otherwise `false`. + */ const checkImageSizeLimit = (size: number): boolean => { return size > 0 && size <= 20000; }; +/** + * Calculates the size of the base64 encoded string in kilobytes. + * + * @param base64String - The base64 encoded string representing the image data. + * @returns The size of the image data in kilobytes. + */ const base64SizeInKb = (base64String: string): number => { // Count the number of Base64 characters const numBase64Chars = base64String.length; @@ -23,18 +35,32 @@ const base64SizeInKb = (base64String: string): number => { return sizeInKB; }; +/** + * Uploads an encoded image to the server. + * + * @param encodedImageURL - The URL or content of the encoded image to upload. + * @param previousImagePath - Optional. The path of the previous image to delete before uploading the new one. + * @returns The file name of the uploaded image. + */ export const uploadEncodedImage = async ( encodedImageURL: string, previousImagePath?: string | null, ): Promise => { + // Check if the uploaded image URL/content is a valid image file type const isURLValidImage = encodedImageExtentionCheck(encodedImageURL); + // Extract the base64 data from the image URL const data = encodedImageURL.replace(/^data:image\/\w+;base64,/, ""); + + // Calculate the size of the base64 encoded image data in kilobytes const sizeInKb = base64SizeInKb(data); + + // Retrieve the size limit from environment variables or set a default limit const limit = checkImageSizeLimit(Number(process.env.IMAGE_SIZE_LIMIT_KB)) ? Number(process.env.IMAGE_SIZE_LIMIT_KB) - : 3000; + : 3000; // Default limit in kilobytes + // Throw an error if the image size exceeds the allowable limit if (sizeInKb > limit) { throw new errors.ImageSizeLimitExceeded( IMAGE_SIZE_LIMIT_KB.MESSAGE, @@ -43,6 +69,7 @@ export const uploadEncodedImage = async ( ); } + // Throw an error if the uploaded image is not a valid file type if (!isURLValidImage) { throw new errors.InvalidFileTypeError( requestContext.translate(INVALID_FILE_TYPE.MESSAGE), @@ -51,10 +78,12 @@ export const uploadEncodedImage = async ( ); } + // Check if the encoded image already exists in the database const encodedImageAlreadyExist = await EncodedImage.findOne({ content: encodedImageURL, }); + // If the image already exists, increment its numberOfUses and handle previousImagePath if (encodedImageAlreadyExist) { if (encodedImageAlreadyExist?.fileName === previousImagePath) { return encodedImageAlreadyExist?.fileName; @@ -78,10 +107,12 @@ export const uploadEncodedImage = async ( return encodedImageAlreadyExist.fileName; } + // Handle deletion of previous image if previousImagePath is provided if (previousImagePath) { await deletePreviousImage(previousImagePath); } + // Generate a unique ID for the new image file using nanoid let id = nanoid(); id = "images/" + id + "image.png"; @@ -91,8 +122,10 @@ export const uploadEncodedImage = async ( content: encodedImageURL, }); + // Convert the base64 data into a buffer const buf = Buffer.from(data, "base64"); + // Create an 'images' directory if it doesn't exist if (!fs.existsSync(path.join(__dirname, "../../../images"))) { fs.mkdir(path.join(__dirname, "../../../images"), (err) => { if (err) { @@ -101,7 +134,9 @@ export const uploadEncodedImage = async ( }); } + // Write the image data to the file system await writeFile(path.join(__dirname, "../../../" + id), buf); + // Return the fileName of the uploaded image return uploadedEncodedImage.fileName; }; diff --git a/src/utilities/encodedVideoStorage/deletePreviousVideo.ts b/src/utilities/encodedVideoStorage/deletePreviousVideo.ts index 7cc8ca1035..40f248e60a 100644 --- a/src/utilities/encodedVideoStorage/deletePreviousVideo.ts +++ b/src/utilities/encodedVideoStorage/deletePreviousVideo.ts @@ -2,20 +2,32 @@ import { unlink } from "fs/promises"; import path from "path"; import { EncodedVideo } from "../../models/EncodedVideo"; +/** + * Deletes the previous video file and updates its database entry. + * + * @param videoToBeDeletedPath - The path of the video file to be deleted. + * @returns A promise that resolves once the video file and database entry are deleted or updated. + */ export const deletePreviousVideo = async ( videoToBeDeletedPath: string, ): Promise => { + // Find the EncodedVideo document corresponding to the video file const videoToBeDeleted = await EncodedVideo.findOne({ fileName: videoToBeDeletedPath, }); + // Check if the video file exists and has only one use left if (videoToBeDeleted?.numberOfUses === 1) { + // Delete the video file from the file system await unlink(path.join(__dirname, "../../../" + videoToBeDeleted.fileName)); + + // Delete the EncodedVideo document from the database await EncodedVideo.deleteOne({ fileName: videoToBeDeletedPath, }); } + // Decrease the numberOfUses in the database for the video file await EncodedVideo.findOneAndUpdate( { fileName: videoToBeDeletedPath, diff --git a/src/utilities/encodedVideoStorage/encodedVideoExtensionCheck.ts b/src/utilities/encodedVideoStorage/encodedVideoExtensionCheck.ts index f9cb626d04..290cb28042 100644 --- a/src/utilities/encodedVideoStorage/encodedVideoExtensionCheck.ts +++ b/src/utilities/encodedVideoStorage/encodedVideoExtensionCheck.ts @@ -1,11 +1,18 @@ +/** + * Checks if the provided base64 encoded URL represents a video with the "mp4" extension. + * @param encodedUrl - The base64 encoded URL of the video. + * @returns `true` if the encoded URL is a valid mp4 video, `false` otherwise. + */ export const encodedVideoExtentionCheck = (encodedUrl: string): boolean => { + // Extract the extension from the encoded URL const extension = encodedUrl.substring( - "data:".length, - encodedUrl.indexOf(";base64"), + "data:".length, // Start after "data:" + encodedUrl.indexOf(";base64"), // End before ";base64" ); - console.log(extension); + console.log(extension); // Log the extracted extension for debugging purposes + // Check if the extension matches "video/mp4" const isValidVideo = extension === "video/mp4"; if (isValidVideo) { return true; diff --git a/src/utilities/encodedVideoStorage/uploadEncodedVideo.ts b/src/utilities/encodedVideoStorage/uploadEncodedVideo.ts index fc01f9b264..c34274e961 100644 --- a/src/utilities/encodedVideoStorage/uploadEncodedVideo.ts +++ b/src/utilities/encodedVideoStorage/uploadEncodedVideo.ts @@ -8,11 +8,18 @@ import { EncodedVideo } from "../../models/EncodedVideo"; import path from "path"; import { deletePreviousVideo } from "./deletePreviousVideo"; +/** + * Uploads an encoded video to the server. + * + * @param encodedVideoURL - The URL or content of the encoded video to upload. + * @param previousVideoPath - Optional. The path of the previous video to delete before uploading the new one. + * @returns The file name of the uploaded video. + */ export const uploadEncodedVideo = async ( encodedVideoURL: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars previousVideoPath?: string | null, ): Promise => { + // Check if the uploaded video URL/content is a valid video file type const isURLValidVideo = encodedVideoExtentionCheck(encodedVideoURL); if (!isURLValidVideo) { @@ -23,15 +30,18 @@ export const uploadEncodedVideo = async ( ); } + // Check if the encoded video already exists in the database const encodedVideoAlreadyExist = await EncodedVideo.findOne({ content: encodedVideoURL, }); if (encodedVideoAlreadyExist) { + // If the encoded video already exists and its fileName matches previousVideoPath, return its fileName if (encodedVideoAlreadyExist?.fileName === previousVideoPath) { return encodedVideoAlreadyExist?.fileName; } + // Increment numberOfUses for the existing encoded video in the database await EncodedVideo.findOneAndUpdate( { content: encodedVideoURL, @@ -43,6 +53,7 @@ export const uploadEncodedVideo = async ( }, ); + // Delete the previous video if previousVideoPath is provided if (previousVideoPath) { await deletePreviousVideo(previousVideoPath); } @@ -50,23 +61,26 @@ export const uploadEncodedVideo = async ( return encodedVideoAlreadyExist.fileName; } + // Delete the previous video if previousVideoPath is provided if (previousVideoPath) { await deletePreviousVideo(previousVideoPath); } + // Generate a unique ID for the new video file using nanoid let id = nanoid(); - id = "videos/" + id + "video.mp4"; + // Create a new entry in EncodedVideo collection for the uploaded video const uploadedEncodedVideo = await EncodedVideo.create({ fileName: id, content: encodedVideoURL, }); + // Extract the video data from the URL (assuming it's base64 encoded) const data = encodedVideoURL.replace(/^data:video\/\w+;base64,/, ""); - const buf = Buffer.from(data, "base64"); + // Create a 'videos' directory if it doesn't exist if (!fs.existsSync(path.join(__dirname, "../../../videos"))) { fs.mkdir(path.join(__dirname, "../../../videos"), (error) => { if (error) { @@ -75,7 +89,9 @@ export const uploadEncodedVideo = async ( }); } + // Write the video data to the file system await writeFile(path.join(__dirname, "../../../" + id), buf); + // Return the fileName of the uploaded video return uploadedEncodedVideo.fileName; }; diff --git a/src/utilities/imageAlreadyInDbCheck.ts b/src/utilities/imageAlreadyInDbCheck.ts index 7364a08523..1d5b8a3506 100644 --- a/src/utilities/imageAlreadyInDbCheck.ts +++ b/src/utilities/imageAlreadyInDbCheck.ts @@ -6,12 +6,12 @@ import { errors, requestContext } from "../libraries"; import { INVALID_FILE_TYPE } from "../constants"; /** - * This function checks if an image already exists in the database using hash. - * If it does, then point to that image and remove the image just uploaded. - * Else, allow the file to get uploaded. - * @param oldImagePath - Path of image - * @param newImagePath - Does image belong to an item - * @returns file name. + * Checks if an image already exists in the database using its hash value. + * If the image exists, it points to the existing image and removes the newly uploaded image. + * If the image does not exist, it saves the image hash in the database. + * @param oldImagePath - Path of the old image that might be replaced. + * @param newImagePath - Path of the newly uploaded image. + * @returns The file name of the existing image if found; otherwise, undefined. */ export const imageAlreadyInDbCheck = async ( oldImagePath: string | null, @@ -20,6 +20,7 @@ export const imageAlreadyInDbCheck = async ( try { let fileName; + // Function to get the hash value of the new image const getImageHash = (): Promise => new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -32,26 +33,30 @@ export const imageAlreadyInDbCheck = async ( }); }); + // Get the hash value of the new image const hash = await getImageHash(); + // Check if there is an existing image with the same hash value in the database const existingImageHash = await ImageHash.findOne({ hashValue: hash, }).lean(); if (!existingImageHash) { + // If no existing image hash found, create a new entry in the ImageHash collection await ImageHash.create({ hashValue: hash, fileName: newImagePath, numberOfUses: 1, }); } else { + // If an image with the same hash exists, perform duplicate check const imageIsDuplicate = await reuploadDuplicateCheck( oldImagePath, newImagePath, ); if (imageIsDuplicate === false) { - // dont increment if the same user/org is using the same image multiple times for the same use case + // Increment the number of uses if it's not a duplicate await ImageHash.updateOne( { // Increase the number of places this image is used @@ -65,13 +70,16 @@ export const imageAlreadyInDbCheck = async ( ); } + // Delete the newly uploaded image as it's a duplicate deleteDuplicatedImage(newImagePath); - fileName = existingImageHash.fileName; // will include have file already in db if pic is already saved will be null otherwise + // Set the file name to the existing image's file name + fileName = existingImageHash.fileName; } return fileName as string; } catch (error) { + // Handle errors, such as invalid file types throw new errors.ValidationError( [ { diff --git a/src/utilities/imageExtensionCheck.ts b/src/utilities/imageExtensionCheck.ts index 337e3f806d..6fbea005d6 100644 --- a/src/utilities/imageExtensionCheck.ts +++ b/src/utilities/imageExtensionCheck.ts @@ -1,11 +1,12 @@ import { deleteImage } from "./deleteImage"; import { errors, requestContext } from "../libraries"; import { INVALID_FILE_TYPE } from "../constants"; + /** - * This function checks the extension of the file. - * If the extension isn't of type 'png', or 'jpg', or 'jpeg', - * then the file is deleted and a validation error is thrown. - * @param filename - Name of file + * Checks the file extension of the given filename. + * If the extension is not 'png', 'jpg', or 'jpeg', deletes the file and throws a validation error. + * + * @param filename - The name of the file to check */ export const imageExtensionCheck = async (filename: string): Promise => { const fileExtension = filename.split(".").pop(); @@ -15,8 +16,10 @@ export const imageExtensionCheck = async (filename: string): Promise => { fileExtension !== "jpg" && fileExtension !== "jpeg" ) { + // Delete the file because the extension is not allowed await deleteImage(filename); + // Throw a validation error indicating invalid file type throw new errors.ValidationError( [ { diff --git a/src/utilities/loadDefaultOrg.ts b/src/utilities/loadDefaultOrg.ts index c7714d98df..0bf21c59fb 100644 --- a/src/utilities/loadDefaultOrg.ts +++ b/src/utilities/loadDefaultOrg.ts @@ -3,21 +3,33 @@ import path from "path"; import { connect, disconnect } from "../db"; import { AppUserProfile, Organization, User } from "../models"; +/** + * Loads default organization data into the database. + * @param dbName - Optional name of the database to connect to. + * @returns Promise + */ export async function loadDefaultOrganiation(dbName?: string): Promise { try { + // Connect to the database await connect(dbName); + + // Read and insert default user data const userData = await fs.readFile( path.join(__dirname, `../../sample_data/defaultUser.json`), "utf8", ); const userDocs = JSON.parse(userData) as Record[]; await User.insertMany(userDocs); + + // Read and insert default app user profile data const appUserData = await fs.readFile( path.join(__dirname, `../../sample_data/defaultAppUserProfile.json`), "utf8", ); const appUserDocs = JSON.parse(appUserData) as Record[]; await AppUserProfile.insertMany(appUserDocs); + + // Read and insert default organization data const organizationData = await fs.readFile( path.join(__dirname, `../../sample_data/defaultOrganization.json`), "utf8", @@ -28,8 +40,10 @@ export async function loadDefaultOrganiation(dbName?: string): Promise { >[]; await Organization.insertMany(organizationDocs); + // Log success message console.log("Default organization loaded"); } catch (error) { + // Log any errors that occur during the process console.log(error); } finally { await disconnect(); // Close the database connection diff --git a/src/utilities/loadSampleData.ts b/src/utilities/loadSampleData.ts index 09c4760810..6309770018 100644 --- a/src/utilities/loadSampleData.ts +++ b/src/utilities/loadSampleData.ts @@ -19,6 +19,9 @@ interface InterfaceArgs { _: unknown; } +/** + * Lists sample data files and their document counts in the sample_data directory. + */ async function listSampleData(): Promise { try { const sampleDataPath = path.join(__dirname, "../../sample_data"); @@ -50,6 +53,9 @@ async function listSampleData(): Promise { } } +/** + * Clears all collections in the database. + */ async function formatDatabase(): Promise { // Clear all collections await Promise.all([ @@ -65,6 +71,10 @@ async function formatDatabase(): Promise { console.log("Cleared all collections\n"); } +/** + * Inserts data into specified collections. + * @param collections - Array of collection names to insert data into + */ async function insertCollections(collections: string[]): Promise { try { // Connect to MongoDB database @@ -141,6 +151,9 @@ async function insertCollections(collections: string[]): Promise { } } +/** + * Checks document counts in specified collections after data insertion. + */ async function checkCountAfterImport(): Promise { try { // Connect to MongoDB database diff --git a/src/utilities/mailer.ts b/src/utilities/mailer.ts index f7b5f93bd3..cfe5b46d44 100644 --- a/src/utilities/mailer.ts +++ b/src/utilities/mailer.ts @@ -8,18 +8,21 @@ import { SMTP_OPTIONS, } from "../constants"; +/** + * Interface for the fields required to send an email. + */ export interface InterfaceMailFields { - emailTo: string; - subject: string; - body: string; + emailTo: string; // Email address of the recipient + subject: string; // Subject of the email + body: string; // Body content of the email (HTML format) } + /** - * This function sends emails to the specified user using the node mailer module. + * Sends an email using Nodemailer. * @remarks - * This is a utility method. - * - * @param InterfaceMailFields - `Interface` type with emailTo(`string`), subject(`string`), and body(`string`) necessary attributes. - * @returns Promise along with resolve and reject methods. + * This is a utility method for sending emails. + * @param mailFields - An object containing emailTo, subject, and body fields. + * @returns A promise resolving to `SMTPTransport.SentMessageInfo` on success, or an error string on failure. */ export const mailer = ( mailFields: InterfaceMailFields, @@ -27,8 +30,7 @@ export const mailer = ( // Nodemailer configuration let transporter: Transporter; - // For using custom smtp server - /* c8 ignore next 12 */ + // Check if custom SMTP server is configured if (SMTP_OPTIONS.IS_SMTP) { transporter = nodemailer.createTransport({ host: String(SMTP_OPTIONS.SMTP_HOST), @@ -39,8 +41,8 @@ export const mailer = ( pass: SMTP_OPTIONS.SMTP_PASSWORD, }, } as SMTPTransport.Options); - // For using gmail transporter } else { + // Use Gmail transporter if custom SMTP is not configured transporter = nodemailer.createTransport({ service: "gmail", auth: { @@ -51,7 +53,6 @@ export const mailer = ( } const mailOptions = { - /* c8 ignore next 6 */ from: !SMTP_OPTIONS.IS_SMTP ? "Talawa<>noreply@gmail.com" : SMTP_OPTIONS.SMTP_USERNAME, @@ -59,13 +60,17 @@ export const mailer = ( subject: mailFields.subject, html: mailFields.body, }; + return new Promise((resolve, reject) => { + // Send email using transporter transporter.sendMail( mailOptions, function (error: Error | null, info: SMTPTransport.SentMessageInfo) { if (error) { + // Handle error if sending mail fails reject(ERROR_IN_SENDING_MAIL); } else { + // Resolve with sent message information if email is sent successfully resolve(info); } }, diff --git a/src/utilities/removeSampleOrganizationUtil.ts b/src/utilities/removeSampleOrganizationUtil.ts index d911407958..089e97368a 100644 --- a/src/utilities/removeSampleOrganizationUtil.ts +++ b/src/utilities/removeSampleOrganizationUtil.ts @@ -8,11 +8,20 @@ import { User, } from "../models"; +/** + * Removes sample organization data from respective collections based on entries in SampleData collection. + * Also deletes all documents in SampleData collection after removal. + * @returns Promise + */ export async function removeSampleOrganization(): Promise { + // Retrieve all documents from SampleData collection const sampleDataDocuments = await SampleData.find({}); + // Iterate through each document in SampleData for (const document of sampleDataDocuments) { const { collectionName, documentId } = document; + + // Define a mapping of collection names to their respective Mongoose models const collectionModels = { Organization, Post, @@ -22,11 +31,14 @@ export async function removeSampleOrganization(): Promise { AppUserProfile, }; + // Determine the model based on collectionName retrieved from SampleData const collectionModel = collectionModels[ collectionName ] as typeof Organization; + // Safely cast the model to its appropriate type and delete the document by ID await collectionModel.findByIdAndDelete(documentId); } + // Delete all documents from SampleData collection after cleanup await SampleData.deleteMany({}); } diff --git a/src/utilities/reuploadDuplicateCheck.ts b/src/utilities/reuploadDuplicateCheck.ts index dedff2eabf..6f92297afb 100644 --- a/src/utilities/reuploadDuplicateCheck.ts +++ b/src/utilities/reuploadDuplicateCheck.ts @@ -1,66 +1,70 @@ import { imageHash } from "image-hash"; import { requestContext, errors, logger } from "../libraries"; +// Interface for URL request object interface InterfaceUrlRequestObject { encoding?: string | null; url: string | null; } +// Interface for Buffer object interface InterfaceBufferObject { ext?: string; data: Buffer; name?: string; } +// Type definition for image path, can be string, InterfaceUrlRequestObject, or InterfaceBufferObject export type TypeImagePath = | string | InterfaceUrlRequestObject | InterfaceBufferObject; +/** + * Gets the hash value of an image using the image-hash library. + * @param oldSrc - Path of the image to hash, can be a string, URL request object, or buffer object. + * @returns Promise that resolves to the hash object. + */ const getImageHash = (oldSrc: TypeImagePath): object => { return new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any imageHash(oldSrc, 16, true, (error: Error, data: any) => { if (error) { - reject(error); + reject(error); // Reject promise if error occurs during hashing } - - resolve(data); + resolve(data); // Resolve promise with hash data }); }); }; + /** - * This function determines whether a user or an organisation is - * attempting to re-upload the same profile photo or organisation image. - * + * Checks if a user or organization is attempting to re-upload the same image. * @remarks * This is a utility method. - * - * @param oldImagePath - Path of a current Org/User image of `type: TypeImagePath`. - * @param newImagePath - Path of a new image of `type: TypeImagePath`. - * @returns If the identical image is trying to reuploaded, `true`; otherwise, `false`. + * @param oldImagePath - Path of the current image (could be a string, URL request object, or buffer object). + * @param newImagePath - Path of the new image being uploaded (could be a string, URL request object, or buffer object). + * @returns Promise that resolves to true if the images are identical, false otherwise. */ export const reuploadDuplicateCheck = async ( oldImagePath: TypeImagePath | null, newImagePath: TypeImagePath, ): Promise => { - /* - This function checks whether a user is trying to re-upload the same profile picture - or an organization is trying to re-upload the same organization image - */ try { if (oldImagePath) { + // Calculate hash of old and new images const oldImageHash = await getImageHash(oldImagePath); - const newImageHash = await getImageHash(newImagePath); + // Compare hashes to determine if images are identical return oldImageHash === newImageHash; } + // If oldImagePath is null, cannot be a duplicate upload return false; } catch (error) { - logger.error(error); + logger.error(error); // Log error for debugging purposes + // Throw a validation error with translated message throw new errors.ValidationError( [ { diff --git a/src/utilities/superAdminCheck.ts b/src/utilities/superAdminCheck.ts index 9074f12a87..7e49693a72 100644 --- a/src/utilities/superAdminCheck.ts +++ b/src/utilities/superAdminCheck.ts @@ -2,6 +2,12 @@ import { USER_NOT_AUTHORIZED_SUPERADMIN } from "../constants"; import { errors, requestContext } from "../libraries"; import type { InterfaceAppUserProfile } from "../models"; +/** + * Checks if the provided application user profile is a super admin. + * Throws an UnauthorizedError if the user is not a super admin. + * + * @param appUserProfile - The user profile of the application. + */ export const superAdminCheck = ( appUserProfile: InterfaceAppUserProfile, ): void => { diff --git a/src/utilities/uploadImage.ts b/src/utilities/uploadImage.ts index 962734c437..c99a44a8a5 100644 --- a/src/utilities/uploadImage.ts +++ b/src/utilities/uploadImage.ts @@ -5,15 +5,15 @@ import { logger } from "../libraries"; import { imageAlreadyInDbCheck } from "./imageAlreadyInDbCheck"; import { deleteImage } from "./deleteImage"; import { imageExtensionCheck } from "./imageExtensionCheck"; + /** - * This function uploads the new image and deletes the previously uploaded image if exists. + * Uploads a new image, deletes the previously uploaded image if it exists, and checks for duplicates in the database. * @remarks * This is a utility method. - * @param newImageFile - File of a new Image with `TypeNewImageFile` type. - * @param oldImagePath - File of a current Image. It can be `null`. - * @returns Path of an uploaded image. + * @param newImageFile - File object of the new image with `TypeNewImageFile` type. + * @param oldImagePath - Path of the current image to be replaced. Can be `null` if no image exists. + * @returns An object containing paths of the newly uploaded image and any duplicate image found in the database. */ - type TypeNewImageFile = { createReadStream: () => NodeJS.ReadStream; filename: string; @@ -23,11 +23,13 @@ export const uploadImage = async ( newImageFile: TypeNewImageFile, oldImagePath: string | null, ): Promise<{ newImagePath: string; imageAlreadyInDbPath: string }> => { + // Generate a unique ID for the new image file const id = nanoid(); + // Extract filename from new image file const { createReadStream, filename } = await newImageFile; - // throw an error if file is not png or jpg + // Validate image file extension (must be PNG or JPG) await imageExtensionCheck(filename); // upload new image @@ -49,6 +51,7 @@ export const uploadImage = async ( const newImagePath = `images/${id}-${filename}`; + // If there is an old image path, delete it and perform duplicate check if (oldImagePath !== null) { console.log("oldImagePath is not null"); @@ -58,11 +61,13 @@ export const uploadImage = async ( await deleteImage(oldImagePath, newImagePath); } + // Check if the newly uploaded image already exists in the database const imageAlreadyInDbPath = await imageAlreadyInDbCheck( oldImagePath, newImagePath, ); + // Return paths of the newly uploaded image and any duplicate found in the database return { newImagePath, imageAlreadyInDbPath, diff --git a/src/utilities/userFamilyAdminCheck.ts b/src/utilities/userFamilyAdminCheck.ts index 95bc2a4e1a..ea17c934c4 100644 --- a/src/utilities/userFamilyAdminCheck.ts +++ b/src/utilities/userFamilyAdminCheck.ts @@ -4,32 +4,36 @@ import { USER_NOT_AUTHORIZED_ADMIN } from "../constants"; import { errors, requestContext } from "../libraries"; import { AppUserProfile } from "../models"; import type { InterfaceUserFamily } from "../models/userFamily"; + /** - * If the current user is an admin of the organisation, this function returns `true` otherwise it returns `false`. + * Checks if the current user is an admin of the organization or a super admin. + * Throws an UnauthorizedError if the user is neither an admin nor a super admin. + * * @remarks - * This is a utility method. - * @param userId - Current user id. - * @param userFamily - userFamily data of `InterfaceuserFamily` type. - * @returns `True` or `False`. + * This function queries the `userFamily` to check if the `userId` is listed as an admin. + * Additionally, it queries the `AppUserProfile` to check if the `userId` is a super admin. + * + * @param userId - The ID of the current user. + * @param userFamily - The user family data of type `InterfaceUserFamily`. */ export const adminCheck = async ( userId: string | Types.ObjectId, userFamily: InterfaceUserFamily, ): Promise => { + // Check if the user is listed as an admin in userFamily const userIsUserFamilyAdmin = userFamily.admins.some( (admin) => admin === userId || new mongoose.Types.ObjectId(admin.toString()).equals(userId), ); - // const user = await User.findOne({ - // _id: userId, - // }); + // Query AppUserProfile to check if the user is a super admin const appUserProfile = await AppUserProfile.findOne({ userId: userId, }); const isUserSuperAdmin: boolean = appUserProfile?.isSuperAdmin || false; + // If the user is neither an admin nor a super admin, throw UnauthorizedError if (!userIsUserFamilyAdmin && !isUserSuperAdmin) { throw new errors.UnauthorizedError( requestContext.translate(`${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`), diff --git a/tests/helpers/directChat.ts b/tests/helpers/directChat.ts index 96d8888e78..0b4d523b23 100644 --- a/tests/helpers/directChat.ts +++ b/tests/helpers/directChat.ts @@ -9,7 +9,7 @@ import { createTestUserAndOrganization } from "./userAndOrg"; import type { Document } from "mongoose"; export type TestDirectChatType = - | (InterfaceDirectChat & Document) + | (InterfaceDirectChat & Document) | null; export type TestDirectChatMessageType = diff --git a/tests/helpers/groupChat.ts b/tests/helpers/groupChat.ts index a7a53b96b9..a41591c962 100644 --- a/tests/helpers/groupChat.ts +++ b/tests/helpers/groupChat.ts @@ -9,7 +9,7 @@ import { createTestUserAndOrganization } from "./userAndOrg"; import type { Document } from "mongoose"; export type TestGroupChatType = - | (InterfaceGroupChat & Document) + | (InterfaceGroupChat & Document) | null; export type TestGroupChatMessageType = diff --git a/tests/resolvers/Fund/campaign.spec.ts b/tests/resolvers/Fund/campaign.spec.ts deleted file mode 100644 index 9635a390c2..0000000000 --- a/tests/resolvers/Fund/campaign.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type mongoose from "mongoose"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { FundraisingCampaign } from "../../../src/models"; -import { campaigns as campaignsResolver } from "../../../src/resolvers/Fund/campaigns"; -import { createTestFund, type TestFundType } from "../../helpers/Fund"; -import { connect, disconnect } from "../../helpers/db"; -let MONGOOSE_INSTANCE: typeof mongoose; -let testFund: TestFundType; -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestFund(); - testFund = temp[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); -describe("resolvers->Fund->fundCampaign", () => { - it("returns all campaigns for parent fund", async () => { - const parent = testFund?.toObject(); - if (parent) { - const campaignsPayload = await campaignsResolver?.(parent, {}, {}); - const campaigns = await FundraisingCampaign.find({ - fundId: testFund?._id, - }).lean(); - expect(campaignsPayload).toEqual(campaigns); - } - }); -}); diff --git a/tests/resolvers/FundraisingCampaign/parentfund.spec.ts b/tests/resolvers/FundraisingCampaign/parentfund.spec.ts deleted file mode 100644 index f54c11de9c..0000000000 --- a/tests/resolvers/FundraisingCampaign/parentfund.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { Fund, type InterfaceFundraisingCampaign } from "../../../src/models"; -import { fundId as fundResolvers } from "../../../src/resolvers/FundraisingCampagin/parentFund"; -import { createTestFund, type TestFundType } from "../../helpers/Fund"; -import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; -import { connect, disconnect } from "../../helpers/db"; -let MONGOOSE_INSTANCE: typeof mongoose; -let testFund: TestFundType; -let testFundCampaigns: InterfaceFundraisingCampaign; -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestFund(); - testFund = temp[2]; - testFundCampaigns = await createTestFundraisingCampaign(testFund?._id); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); -describe("resolvers->FundrasingCampaign->parentFund", () => { - it("returns the parent fund for campaign", async () => { - const parent = testFundCampaigns; - if (parent) { - const fundPayload = await fundResolvers?.(parent, {}, {}); - const fund = await Fund.findOne({ - _id: new Types.ObjectId(testFundCampaigns?.fundId?.toString()), - }).lean(); - - expect(fundPayload?._id).toEqual(fund?._id); - } - }); -}); diff --git a/tests/resolvers/FundraisingCampaign/pledge.spec.ts b/tests/resolvers/FundraisingCampaign/pledge.spec.ts deleted file mode 100644 index 0a3a9103ee..0000000000 --- a/tests/resolvers/FundraisingCampaign/pledge.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type mongoose from "mongoose"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { InterfaceFundraisingCampaign } from "../../../src/models"; -import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; -import { pledges as pledgesResolver } from "../../../src/resolvers/FundraisingCampagin/campaignPledges"; -import { createTestFund, type TestFundType } from "../../helpers/Fund"; -import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; -import { connect, disconnect } from "../../helpers/db"; -let MONGOOSE_INSTANCE: typeof mongoose; -let testFund: TestFundType; -let testFundCampaigns: InterfaceFundraisingCampaign; -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestFund(); - testFund = temp[2]; - testFundCampaigns = await createTestFundraisingCampaign(testFund?._id); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); -describe("resolvers->FundrasingCampaign->pledge", () => { - it("returns all pledges for parent campaign", async () => { - const parent = testFundCampaigns; - if (parent) { - const pledgesPayload = await pledgesResolver?.(parent, {}, {}); - const pledges = await FundraisingCampaignPledge.find({ - campaigns: testFundCampaigns?._id, - }).lean(); - expect(pledgesPayload).toEqual(pledges); - } - }); -}); diff --git a/tests/resolvers/Mutation/createDirectChat.spec.ts b/tests/resolvers/Mutation/createDirectChat.spec.ts index ee51c5e9d2..7c46e94092 100644 --- a/tests/resolvers/Mutation/createDirectChat.spec.ts +++ b/tests/resolvers/Mutation/createDirectChat.spec.ts @@ -123,7 +123,6 @@ describe("resolvers -> Mutation -> createDirectChat", () => { expect.objectContaining({ creatorId: testUser?._id, users: [testUser?._id], - organization: testOrganization?._id, }), ); }); diff --git a/tests/resolvers/Mutation/createFund.spec.ts b/tests/resolvers/Mutation/createFund.spec.ts index d0a45bfd0b..e6c21e8983 100644 --- a/tests/resolvers/Mutation/createFund.spec.ts +++ b/tests/resolvers/Mutation/createFund.spec.ts @@ -4,7 +4,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { FUND_ALREADY_EXISTS, ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ADMIN, USER_NOT_AUTHORIZED_ERROR, USER_NOT_FOUND_ERROR, } from "../../../src/constants"; @@ -16,10 +15,7 @@ import type { TestOrganizationType, TestUserType, } from "../../helpers/userAndOrg"; -import { - createTestUser, - createTestUserAndOrganization, -} from "../../helpers/userAndOrg"; +import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; let testUser: TestUserType; let testOrganization: TestOrganizationType; @@ -83,29 +79,7 @@ describe("resolvers-> Mutation-> createFund", () => { ); } }); - it("throw error if the user is not authorized to create the fund", async () => { - try { - const args: MutationCreateFundArgs = { - data: { - organizationId: testOrganization?._id, - name: "testFund", - taxDeductible: true, - isDefault: true, - isArchived: false, - }, - }; - const randomUser = await createTestUser(); - const context = { - userId: randomUser?._id, - }; - await createFund?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ADMIN.MESSAGE, - ); - } - }); it("creates fund with provided data", async () => { const args: MutationCreateFundArgs = { data: { diff --git a/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts b/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts index 0927285634..ff1b994364 100644 --- a/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts +++ b/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts @@ -4,7 +4,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MADE_PLEDGE_ERROR, } from "../../../src/constants"; import { FundraisingCampaign, @@ -17,7 +16,7 @@ import { type TestPledgeType, } from "../../helpers/FundraisingCampaignPledge"; import { connect, disconnect } from "../../helpers/db"; -import { createTestUser, type TestUserType } from "../../helpers/userAndOrg"; +import { type TestUserType } from "../../helpers/userAndOrg"; let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; let testCampaign: InterfaceFundraisingCampaign; @@ -70,22 +69,6 @@ describe("resolvers->Mutation->removeFund", () => { ); } }); - it("throw error if user has not made the pledge", async () => { - try { - const randomUser = await createTestUser(); - const args: MutationRemoveFundraisingCampaignPledgeArgs = { - id: testPledge?._id.toString() || "", - }; - const context = { - userId: randomUser?._id.toString() || "", - }; - await removeFundraisingCampaignPledge?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_MADE_PLEDGE_ERROR.MESSAGE, - ); - } - }); it("remove the pledge", async () => { const args: MutationRemoveFundraisingCampaignPledgeArgs = { id: testPledge?._id.toString() || "", diff --git a/tests/resolvers/Mutation/updateAgendaItem.spec.ts b/tests/resolvers/Mutation/updateAgendaItem.spec.ts index 9c9424bff0..e123b70656 100644 --- a/tests/resolvers/Mutation/updateAgendaItem.spec.ts +++ b/tests/resolvers/Mutation/updateAgendaItem.spec.ts @@ -108,7 +108,7 @@ describe("resolvers -> Mutation -> updateAgendaItem", () => { const args: MutationUpdateAgendaItemArgs = { id: "", input: { - updatedBy: new Types.ObjectId().toString(), + title: "Test Item New", }, }; @@ -126,7 +126,7 @@ describe("resolvers -> Mutation -> updateAgendaItem", () => { const args: MutationUpdateAgendaItemArgs = { id: new Types.ObjectId().toString(), input: { - updatedBy: testAdminUser?._id, + title: "Test Item New", }, }; @@ -146,7 +146,7 @@ describe("resolvers -> Mutation -> updateAgendaItem", () => { const args: MutationUpdateAgendaItemArgs = { id: testAgendaItem._id.toString(), input: { - updatedBy: testUser?._id, + title: "Test Item New", }, }; @@ -165,7 +165,6 @@ describe("resolvers -> Mutation -> updateAgendaItem", () => { const args: MutationUpdateAgendaItemArgs = { id: testAgendaItem._id.toString(), input: { - updatedBy: testAdminUser?._id, title: "Test Item New", duration: "One hour plus extra time ", relatedEvent: testEvent?._id.toString(), @@ -199,7 +198,6 @@ describe("resolvers -> Mutation -> updateAgendaItem", () => { id: testAgendaItem?._id, input: { description: "Updated Description", - updatedBy: testUser?._id, }, }; diff --git a/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts b/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts index 41e67c2407..096c57e5c5 100644 --- a/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts +++ b/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts @@ -6,7 +6,6 @@ import { FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR, START_DATE_VALIDATION_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MADE_PLEDGE_ERROR, } from "../../../src/constants"; import { type InterfaceFundraisingCampaign } from "../../../src/models"; import { updateFundraisingCampaignPledge } from "../../../src/resolvers/Mutation/updateFundCampaignPledge"; @@ -17,9 +16,8 @@ import { } from "../../helpers/FundraisingCampaignPledge"; import { connect, disconnect } from "../../helpers/db"; import type { TestUserType } from "../../helpers/user"; -import { createTestUser } from "../../helpers/userAndOrg"; -let testUser: TestUserType; +let testUser: TestUserType; let testcampaignPledge: TestPledgeType; let testFundraisingCampaign: InterfaceFundraisingCampaign; let MONGOOSE_INSTANCE: typeof mongoose; @@ -35,9 +33,11 @@ beforeAll(async () => { testFundraisingCampaign = temp[3]; testcampaignPledge = temp[4]; }); + afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); + describe("resolvers->Mutation->updateFundCampaignPledge", () => { it("throw error if no user exists with _id===context.userId", async () => { try { @@ -58,7 +58,7 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); - it("throw errpr if no plege exists with _id===args.id", async () => { + it("throw error if no plege exists with _id===args.id", async () => { try { const args: MutationUpdateFundraisingCampaignPledgeArgs = { id: new Types.ObjectId().toString() || "", @@ -78,27 +78,7 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { ); } }); - it("throw error if user has not made the pledge", async () => { - try { - const randomUser = await createTestUser(); - const args: MutationUpdateFundraisingCampaignPledgeArgs = { - id: testcampaignPledge?._id.toString() || "", - data: { - startDate: new Date(new Date().toDateString()), - endDate: new Date(new Date().toDateString()), - currency: "USD", - }, - }; - const context = { - userId: randomUser?._id.toString() || "", - }; - await updateFundraisingCampaignPledge?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_MADE_PLEDGE_ERROR.MESSAGE, - ); - } - }); + it("throws error if startDate is invalid", async () => { try { const args: MutationUpdateFundraisingCampaignPledgeArgs = { @@ -131,10 +111,6 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { }, }; - // console.log( - // testcampaignPledge?.users.includes(testUser?._id), - // testUser?._id, - // ); const context = { userId: testUser?._id.toString(), }; @@ -145,6 +121,7 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { ); } }); + it("should update the pledge", async () => { const args: MutationUpdateFundraisingCampaignPledgeArgs = { id: testcampaignPledge?._id.toString() || "", @@ -161,4 +138,21 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { const pledge = await updateFundraisingCampaignPledge?.({}, args, context); expect(pledge).toBeTruthy(); }); + + it("throws an error when an invalid user ID is provided", async () => { + try { + const args = { + id: testcampaignPledge?._id.toString() || "", + data: { + users: [new Types.ObjectId().toString()], + }, + }; + const context = { + userId: testUser?._id.toString(), + }; + await updateFundraisingCampaignPledge?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); }); diff --git a/tests/resolvers/Organization/userTags.spec.ts b/tests/resolvers/Organization/userTags.spec.ts new file mode 100644 index 0000000000..d3d3e9b948 --- /dev/null +++ b/tests/resolvers/Organization/userTags.spec.ts @@ -0,0 +1,118 @@ +import "dotenv/config"; +import { GraphQLError } from "graphql"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { InterfaceOrganization } from "../../../src/models"; +import { OrganizationTagUser } from "../../../src/models"; +import { + parseCursor, + userTags as userTagsResolver, +} from "../../../src/resolvers/Organization/userTags"; +import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; +import { connect, disconnect } from "../../helpers/db"; +import type { TestUserTagType } from "../../helpers/tags"; +import { createRootTagsWithOrg } from "../../helpers/tags"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUserTag1: TestUserTagType, testUserTag2: TestUserTagType; +let testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [, testOrganization, [testUserTag1, testUserTag2]] = + await createRootTagsWithOrg(2); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("userTags resolver", () => { + const parent = testOrganization?.toObject() as InterfaceOrganization; + it(`throws GraphQLError if invalid arguments are provided to the resolver`, async () => { + try { + await userTagsResolver?.(parent, {}, {}); + } catch (error) { + if (error instanceof GraphQLError) { + expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); + expect( + (error.extensions.errors as DefaultGraphQLArgumentError[]).length, + ).toBeGreaterThan(0); + } + } + }); + + it(`returns the expected connection object`, async () => { + const parent = testOrganization?.toObject() as InterfaceOrganization; + + const connection = await userTagsResolver?.( + parent, + { + first: 2, + }, + {}, + ); + + const totalCount = await OrganizationTagUser.find({ + organizationId: testOrganization?._id, + }).countDocuments(); + + expect(connection).toEqual({ + edges: [ + { + cursor: testUserTag2?._id.toString(), + node: { + ...testUserTag2, + _id: testUserTag2?._id.toString(), + }, + }, + { + cursor: testUserTag1?._id.toString(), + node: { + ...testUserTag1, + _id: testUserTag1?._id.toString(), + }, + }, + ], + pageInfo: { + endCursor: testUserTag1?._id.toString(), + hasNextPage: false, + hasPreviousPage: false, + startCursor: testUserTag2?._id.toString(), + }, + totalCount, + }); + }); +}); + +describe("parseCursor function", () => { + it("returns failure state if argument cursorValue is an invalid cursor", async () => { + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: new Types.ObjectId().toString(), + organizationId: testOrganization?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(false); + if (result.isSuccessful === false) { + expect(result.errors.length).toBeGreaterThan(0); + } + }); + + it("returns success state if argument cursorValue is a valid cursor", async () => { + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: testUserTag1?._id.toString() as string, + organizationId: testOrganization?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(true); + if (result.isSuccessful === true) { + expect(result.parsedCursor).toEqual(testUserTag1?._id.toString()); + } + }); +}); diff --git a/tests/resolvers/Query/agendaItemByEvent.spec.ts b/tests/resolvers/Query/agendaItemByEvent.spec.ts new file mode 100644 index 0000000000..6b592446b7 --- /dev/null +++ b/tests/resolvers/Query/agendaItemByEvent.spec.ts @@ -0,0 +1,35 @@ +import "dotenv/config"; +import { AgendaItemModel } from "../../../src/models"; +import { connect, disconnect } from "../../helpers/db"; +import type { QueryAgendaItemByEventArgs } from "../../../src/types/generatedGraphQLTypes"; +import { agendaItemByEvent } from "../../../src/resolvers/Query/agendaItemByEvent"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type mongoose from "mongoose"; +import type { TestEventType } from "../../helpers/events"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testEvent: TestEventType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> agendaItemByEvent", () => { + it(`returns list of all items belonging to an event`, async () => { + const args: QueryAgendaItemByEventArgs = { + relatedEventId: testEvent?._id, + }; + + const itemByEventPayload = await agendaItemByEvent?.({}, args, {}); + + const itemByEventInfo = await AgendaItemModel.find({ + relatedEventId: testEvent?._id, + }).lean(); + + expect(itemByEventPayload).toEqual(itemByEventInfo); + }); +}); diff --git a/tests/resolvers/Query/agendaItemByOrganization.spec.ts b/tests/resolvers/Query/agendaItemByOrganization.spec.ts new file mode 100644 index 0000000000..12c27f57eb --- /dev/null +++ b/tests/resolvers/Query/agendaItemByOrganization.spec.ts @@ -0,0 +1,39 @@ +import "dotenv/config"; +import { AgendaItemModel } from "../../../src/models"; +import { connect, disconnect } from "../../helpers/db"; +import type { QueryAgendaItemByOrganizationArgs } from "../../../src/types/generatedGraphQLTypes"; +import { agendaItemByOrganization } from "../../../src/resolvers/Query/agendaItemByOrganization"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type mongoose from "mongoose"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> agendaItemByOrganization", () => { + it(`returns list of all items belonging to an organization`, async () => { + const args: QueryAgendaItemByOrganizationArgs = { + organizationId: testOrganization?._id, + }; + + const itemByOrganizationPayload = await agendaItemByOrganization?.( + {}, + args, + {}, + ); + + const itemByOrganizationInfo = await AgendaItemModel.find({ + organizationId: testOrganization?._id, + }).lean(); + + expect(itemByOrganizationPayload).toEqual(itemByOrganizationInfo); + }); +}); diff --git a/tests/resolvers/Query/directChatById.spec.ts b/tests/resolvers/Query/directChatById.spec.ts new file mode 100644 index 0000000000..0b6e993df1 --- /dev/null +++ b/tests/resolvers/Query/directChatById.spec.ts @@ -0,0 +1,58 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; + +import { directChatById as directChatByIdResolver } from "../../../src/resolvers/Query/directChatById"; +import { DirectChat } from "../../../src/models"; +import type { QueryDirectChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { createTestDirectChat } from "../../helpers/directChat"; +import type { TestDirectChatType } from "../../helpers/directChat"; + +let testDirectChat: TestDirectChatType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestDirectChat(); + testDirectChat = resultArray[2]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> directChatsById", () => { + it(`throws NotFoundError if no directChats exists with directChats._id === args.id`, async () => { + try { + const args: QueryDirectChatsByUserIdArgs = { + id: new Types.ObjectId().toString(), + }; + + await directChatByIdResolver?.({}, args, {}); + } catch (error: unknown) { + expect((error as Error).message).toEqual("Chat not found"); + } + }); + + it(`returns list of all directChats with directChat.users containing the user + with _id === args.id`, async () => { + const args: QueryDirectChatsByUserIdArgs = { + id: testDirectChat?._id, + }; + + const directChatsByUserIdPayload = await directChatByIdResolver?.( + {}, + args, + {}, + ); + + const directChatsByUserId = await DirectChat.findById( + testDirectChat?._id, + ).lean(); + console.log(directChatsByUserIdPayload); + console.log(directChatsByUserId); + expect(directChatsByUserIdPayload).toEqual(directChatsByUserId); + }); +}); diff --git a/tests/resolvers/Query/getFundCampaignById.spec.ts b/tests/resolvers/Query/getFundCampaignById.spec.ts index c1ffb840ad..3a8a7dbed8 100644 --- a/tests/resolvers/Query/getFundCampaignById.spec.ts +++ b/tests/resolvers/Query/getFundCampaignById.spec.ts @@ -6,6 +6,8 @@ import { getFundraisingCampaignById } from "../../../src/resolvers/Query/getFund import { createTestFund, type TestFundType } from "../../helpers/Fund"; import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; import { connect, disconnect } from "../../helpers/db"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; + let MONGOOSE_INSTANCE: typeof mongoose; let testFund: TestFundType; let testCampaign: InterfaceFundraisingCampaign; @@ -25,14 +27,18 @@ describe("resolvers->Query->getFundCampaignById", () => { const args = { id: testCampaign?._id.toString(), }; + const pledges = await FundraisingCampaignPledge.find({ + campaigns: testCampaign?._id, + }).lean(); + console.log(pledges); const getFundCampaignByIdPayload = await getFundraisingCampaignById?.( {}, args, {}, ); - // console.log(getFundCampaignByIdPayload, testCampaign); expect(getFundCampaignByIdPayload?._id).toEqual(testCampaign._id); }); + it(`returns null if campaign not found for args.id`, async () => { const args = { id: new Types.ObjectId().toString(), diff --git a/tests/resolvers/Query/groupChatById.spec.ts b/tests/resolvers/Query/groupChatById.spec.ts new file mode 100644 index 0000000000..770557a301 --- /dev/null +++ b/tests/resolvers/Query/groupChatById.spec.ts @@ -0,0 +1,61 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; + +import { groupChatById as groupChatByIdResolver } from "../../../src/resolvers/Query/groupChatById"; +import { GroupChat } from "../../../src/models"; +import type { + QueryGroupChatByIdArgs, + QueryGroupChatsByUserIdArgs, +} from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { createTestGroupChat } from "../../helpers/groupChat"; +import type { TestGroupChatType } from "../../helpers/groupChat"; + +let testGroupChat: TestGroupChatType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestGroupChat(); + testGroupChat = resultArray[2]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> directChatsById", () => { + it(`throws NotFoundError if no directChats exists with directChats._id === args.id`, async () => { + try { + const args: QueryGroupChatByIdArgs = { + id: new Types.ObjectId().toString(), + }; + + await groupChatByIdResolver?.({}, args, {}); + } catch (error: unknown) { + expect((error as Error).message).toEqual("Chat not found"); + } + }); + + it(`returns list of all directChats with directChat.users containing the user + with _id === args.id`, async () => { + const args: QueryGroupChatsByUserIdArgs = { + id: testGroupChat?._id, + }; + + const directChatsByUserIdPayload = await groupChatByIdResolver?.( + {}, + args, + {}, + ); + + const directChatsByUserId = await GroupChat.findById( + testGroupChat?._id, + ).lean(); + console.log(directChatsByUserIdPayload); + console.log(directChatsByUserId); + expect(directChatsByUserIdPayload).toEqual(directChatsByUserId); + }); +}); diff --git a/tests/resolvers/Query/groupChatsByUserId.spec.ts b/tests/resolvers/Query/groupChatsByUserId.spec.ts new file mode 100644 index 0000000000..7026580b17 --- /dev/null +++ b/tests/resolvers/Query/groupChatsByUserId.spec.ts @@ -0,0 +1,58 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; + +import { groupChatsByUserId as groupChatsByUserIdResolver } from "../../../src/resolvers/Query/groupChatsByUserId"; +import { GroupChat } from "../../../src/models"; +import type { QueryGroupChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { createTestGroupChat } from "../../helpers/groupChat"; +import type { TestUserType } from "../../helpers/userAndOrg"; + +let testUser: TestUserType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestGroupChat(); + testUser = resultArray[0]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> groupChatsByUserId", () => { + it(`throws NotFoundError if no groupChats exists with groupChats.users + containing user with _id === args.id`, async () => { + try { + const args: QueryGroupChatsByUserIdArgs = { + id: new Types.ObjectId().toString(), + }; + + await groupChatsByUserIdResolver?.({}, args, {}); + } catch (error: unknown) { + expect((error as Error).message).toEqual("Group Chats not found"); + } + }); + + it(`returns list of all groupChats with groupChat.users containing the user + with _id === args.id`, async () => { + const args: QueryGroupChatsByUserIdArgs = { + id: testUser?._id, + }; + + const groupChatsByUserIdPayload = await groupChatsByUserIdResolver?.( + {}, + args, + {}, + ); + + const groupChatsByUserId = await GroupChat.find({ + users: testUser?._id, + }).lean(); + + expect(groupChatsByUserIdPayload).toEqual(groupChatsByUserId); + }); +}); diff --git a/tests/resolvers/Query/helperFunctions/getSort.spec.ts b/tests/resolvers/Query/helperFunctions/getSort.spec.ts index 57c070b842..e37476b1f4 100644 --- a/tests/resolvers/Query/helperFunctions/getSort.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getSort.spec.ts @@ -3,8 +3,12 @@ import { getSort } from "../../../../src/resolvers/Query/helperFunctions/getSort import type { EventOrderByInput, OrganizationOrderByInput, + PledgeOrderByInput, PostOrderByInput, UserOrderByInput, + VenueOrderByInput, + FundOrderByInput, + CampaignOrderByInput, } from "../../../../src/types/generatedGraphQLTypes"; describe("getSort function", () => { @@ -13,6 +17,8 @@ describe("getSort function", () => { ["id_DESC", { _id: -1 }], ["title_ASC", { title: 1 }], ["title_DESC", { title: -1 }], + ["amount_ASC", { amount: 1 }], + ["amount_DESC", { amount: -1 }], ["description_ASC", { description: 1 }], ["description_DESC", { description: -1 }], ["startDate_ASC", { startDate: 1 }], @@ -27,6 +33,8 @@ describe("getSort function", () => { ["endTime_DESC", { endTime: -1 }], ["location_ASC", { location: 1 }], ["location_DESC", { location: -1 }], + ["capacity_ASC", { capacity: 1 }], + ["capacity_DESC", { capacity: -1 }], ["createdAt_ASC", { createdAt: 1 }], ["createdAt_DESC", { createdAt: -1 }], ["name_ASC", { name: 1 }], @@ -49,6 +57,8 @@ describe("getSort function", () => { ["likeCount_DESC", { likeCount: -1 }], ["commentCount_ASC", { commentCount: 1 }], ["commentCount_DESC", { commentCount: -1 }], + ["fundingGoal_ASC", { fundingGoal: 1 }], + ["fundingGoal_DESC", { fundingGoal: -1 }], ]; it.each(testCases)( @@ -59,7 +69,11 @@ describe("getSort function", () => { | EventOrderByInput | OrganizationOrderByInput | PostOrderByInput - | UserOrderByInput, + | UserOrderByInput + | VenueOrderByInput + | PledgeOrderByInput + | FundOrderByInput + | CampaignOrderByInput, ); expect(result).toEqual(expected); }, diff --git a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts index 9c706e9727..908f6d611e 100644 --- a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts @@ -9,6 +9,7 @@ import type { PostWhereInput, UserWhereInput, VenueWhereInput, + CampaignWhereInput, } from "../../../../src/types/generatedGraphQLTypes"; describe("getWhere function", () => { @@ -22,6 +23,7 @@ describe("getWhere function", () => { DonationWhereInput & ActionItemWhereInput & FundWhereInput & + CampaignWhereInput & VenueWhereInput >, Record, diff --git a/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts b/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts index 4eab859433..8a6fdff245 100644 --- a/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts +++ b/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts @@ -38,6 +38,9 @@ describe("src -> resolvers -> Subscription -> messageSentToDirectChat", () => { }, context: { currentUserId: testCurrentUser?._id }, }; + const variables = { + userId: testCurrentUser?._id, + }; const payload = { messageSentToDirectChat: { receiver: testDirectChatMessage?.receiver, @@ -53,11 +56,11 @@ describe("src -> resolvers -> Subscription -> messageSentToDirectChat", () => { context, ); expect(x).not.toBe(null); - expect(await filterFunction(payload, context)).toBe(true); + expect(await filterFunction(payload, variables)).toBe(true); // If current User is sender payload.messageSentToDirectChat.receiver = "receiver"; - expect(await filterFunction(payload, context)).toBe(true); + expect(await filterFunction(payload, variables)).toBe(true); }); it("user is not notified if it is not a part of DirectChat", async () => { @@ -76,6 +79,9 @@ describe("src -> resolvers -> Subscription -> messageSentToDirectChat", () => { }, context: { currentUserId: testCurrentUser?._id }, }; + const variables = { + userId: testCurrentUser?._id, + }; const payload = { messageSentToDirectChat: { @@ -92,6 +98,6 @@ describe("src -> resolvers -> Subscription -> messageSentToDirectChat", () => { context, ); expect(x).not.toBe(null); - expect(await filterFunction(payload, context)).toBe(false); + expect(await filterFunction(payload, variables)).toBe(false); }); }); diff --git a/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts b/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts index 5474a0ea8b..c1aa13072a 100644 --- a/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts +++ b/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts @@ -34,6 +34,9 @@ describe("src -> resolvers -> Subscription -> messageSentToGroupChat", () => { }, context: { currentUserId: testGroupChat?.users[0] }, }; + const variables = { + userId: testGroupChat?.users[0], + }; const payload = { messageSentToGroupChat: { groupChatMessageBelongsTo: testGroupChat?._id, @@ -44,7 +47,7 @@ describe("src -> resolvers -> Subscription -> messageSentToGroupChat", () => { // @ts-expect-error-ignore const x = messageSentToGroupChatPayload?.subscribe(_parent, _args, context); expect(x).not.toBe(null); - expect(await filterFunction(payload, context)).toBe(true); + expect(await filterFunction(payload, variables)).toBe(true); }); it("subscription filter function returns false when group chat not found with the id", async () => { const { messageSentToGroupChat: messageSentToGroupChatPayload } = @@ -67,11 +70,14 @@ describe("src -> resolvers -> Subscription -> messageSentToGroupChat", () => { groupChatMessageBelongsTo: new mongoose.Types.ObjectId(), }, }; + const variables = { + userId: testGroupChat?.users[0], + }; // @ts-expect-error-ignore messageSentToGroupChatPayload.payload = payload; // @ts-expect-error-ignore const x = messageSentToGroupChatPayload?.subscribe(_parent, _args, context); expect(x).not.toBe(null); - expect(await filterFunction(payload, context)).toBe(false); + expect(await filterFunction(payload, variables)).toBe(false); }); }); diff --git a/tests/resolvers/User/post.spec.ts b/tests/resolvers/User/post.spec.ts index efb1b1f706..d5efab53d4 100644 --- a/tests/resolvers/User/post.spec.ts +++ b/tests/resolvers/User/post.spec.ts @@ -1,4 +1,5 @@ // Replace with the correct path + import { GraphQLError } from "graphql"; import type mongoose from "mongoose"; import { Types } from "mongoose"; @@ -26,6 +27,7 @@ let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; let testPost: TestPostType; let testPost2: TestPostType; +let testPost3: TestPostType; let testOrganization: TestOrganizationType; beforeAll(async () => { @@ -41,6 +43,12 @@ beforeAll(async () => { organization: testOrganization?._id, pinned: false, }); + testPost3 = await Post.create({ + text: `text${nanoid().toLowerCase()}`, + creatorId: testUser?._id, + organization: testOrganization?._id, + pinned: false, + }); }); afterAll(async () => { @@ -55,23 +63,23 @@ describe("resolvers -> User -> post", () => { await postResolver?.(parent, {}, {}); } catch (error) { if (error instanceof GraphQLError) { - expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); + expect(error.extensions?.code).toEqual("INVALID_ARGUMENTS"); expect( - (error.extensions.errors as DefaultGraphQLArgumentError[]).length, + (error.extensions?.errors as DefaultGraphQLArgumentError[]).length, ).toBeGreaterThan(0); } } }); + it(`returns the expected connection object`, async () => { const parent = testUser as InterfaceUser; const connection = await postResolver?.( parent, { - first: 2, + first: 3, }, {}, ); - console.log(connection, testPost2?._id, testPost?._id); const totalCount = await Post.find({ creatorId: testUser?._id, }).countDocuments(); @@ -81,18 +89,107 @@ describe("resolvers -> User -> post", () => { // Check individual properties // console.log(connection?.edges[0]); expect((connection?.edges[0] as unknown as PostEdge).cursor).toEqual( - testPost2?._id.toString(), + testPost3?._id.toString(), ); expect((connection?.edges[1] as unknown as PostEdge).cursor).toEqual( + testPost2?._id.toString(), + ); + expect((connection?.edges[2] as unknown as PostEdge).cursor).toEqual( testPost?._id.toString(), ); expect(connection?.pageInfo.endCursor).toEqual(testPost?._id.toString()); expect(connection?.pageInfo.hasNextPage).toBe(false); expect(connection?.pageInfo.hasPreviousPage).toBe(false); - expect(connection?.pageInfo.startCursor).toEqual(testPost2?._id.toString()); + expect(connection?.pageInfo.startCursor).toEqual(testPost3?._id.toString()); expect(connection?.totalCount).toEqual(totalCount); }); + + it("returns an empty connection object if no posts are found", async () => { + await Post.deleteMany({ creatorId: testUser?._id }); + const parent = testUser as InterfaceUser; + const connection = await postResolver?.(parent, { first: 2 }, {}); + + expect(connection?.edges).toHaveLength(0); + expect(connection?.totalCount).toEqual(0); + expect(connection?.pageInfo.endCursor).toBeNull(); + expect(connection?.pageInfo.startCursor).toBeNull(); + expect(connection?.pageInfo.hasNextPage).toBe(false); + expect(connection?.pageInfo.hasPreviousPage).toBe(false); + }); + + it("handles different pagination arguments correctly", async () => { + // Recreate posts for pagination testing + testPost = await Post.create({ + text: `text${nanoid().toLowerCase()}`, + creatorId: testUser?._id, + organization: testOrganization?._id, + pinned: false, + }); + testPost2 = await Post.create({ + text: `text${nanoid().toLowerCase()}`, + creatorId: testUser?._id, + organization: testOrganization?._id, + pinned: false, + }); + testPost3 = await Post.create({ + text: `text${nanoid().toLowerCase()}`, + creatorId: testUser?._id, + organization: testOrganization?._id, + pinned: false, + }); + + const parent = testUser as InterfaceUser; + + const connectionFirst = await postResolver?.(parent, { first: 1 }, {}); + expect(connectionFirst?.edges).toHaveLength(1); + expect(connectionFirst?.pageInfo.hasNextPage).toBe(true); + expect(connectionFirst?.pageInfo.hasPreviousPage).toBe(false); + + const connectionLast = await postResolver?.(parent, { last: 1 }, {}); + expect(connectionLast?.edges).toHaveLength(1); + expect(connectionLast?.pageInfo.hasNextPage).toBe(false); + expect(connectionLast?.pageInfo.hasPreviousPage).toBe(true); + }); + + it("throws an error for invalid cursor value", async () => { + const parent = testUser as InterfaceUser; + const args = { after: "invalidCursor", first: 10 }; + await expect(postResolver?.(parent, args, {})).rejects.toThrow(); + }); + + it("handles valid cursor value", async () => { + const parent = testUser as InterfaceUser; + const args = { after: testPost2?._id.toString(), first: 10 }; + const connection = await postResolver?.(parent, args, {}); + expect(connection).toBeDefined(); + expect(connection?.edges.length).toBeGreaterThan(0); + + const allPostIds = [testPost, testPost2, testPost3].map((post) => + post?._id.toString(), + ); + + const returnedCursor = (connection?.edges[0] as unknown as PostEdge).cursor; + expect(allPostIds).toContain(returnedCursor); + expect(returnedCursor).not.toEqual(testPost2?._id.toString()); + }); +}); + +it("handles missing cursor value gracefully", async () => { + const parent = testUser as InterfaceUser; + const args = { first: 10 }; + const connection = await postResolver?.(parent, args, {}); + expect(connection).toBeDefined(); + expect(connection?.edges.length).toBeGreaterThan(0); +}); + +it("handles cursor value with pagination arguments", async () => { + const parent = testUser as InterfaceUser; + const args = { after: testPost?._id.toString(), first: 2 }; + const connection = await postResolver?.(parent, args, {}); + expect(connection).toBeDefined(); + expect(connection?.edges.length).toBeLessThanOrEqual(2); }); + describe("parseCursor function", () => { it("returns failure state if argument cursorValue is an invalid cursor", async () => { const result = await parseCursor({ @@ -121,4 +218,61 @@ describe("parseCursor function", () => { expect(result.parsedCursor).toEqual(testPost?._id.toString()); } }); + + it("returns failure state if creatorId is invalid", async () => { + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: testPost?._id.toString() as string, + creatorId: new Types.ObjectId().toString(), + }); + + expect(result.isSuccessful).toEqual(false); + if (result.isSuccessful === false) { + expect(result.errors.length).toBeGreaterThan(0); + } + }); + + it("handles empty string cursor value", async () => { + try { + await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: "", + creatorId: testUser?._id.toString() as string, + }); + } catch (error) { + expect(error).toBeDefined(); + expect((error as Error).message).toContain("Cast to ObjectId failed"); + } + }); + + it("handles invalid ObjectId string as cursor value", async () => { + try { + await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: "invalidObjectId", + creatorId: testUser?._id.toString() as string, + }); + } catch (error) { + expect(error).toBeDefined(); + expect((error as Error).message).toContain("Cast to ObjectId failed"); + } + }); + + it("handles non-existent ObjectId as cursor value", async () => { + const nonExistentId = new Types.ObjectId().toString(); + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: nonExistentId, + creatorId: testUser?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(false); + if (result.isSuccessful === false) { + expect(result.errors.length).toBeGreaterThan(0); + } + }); });