From c683fd853615371de4004b9eec35a0a6c326a3ef Mon Sep 17 00:00:00 2001 From: xoldd Date: Sun, 8 Dec 2024 19:06:39 +0530 Subject: [PATCH] add graphql implementation for post comments (#2741) * updated dependencies * update drizzle schema updated drizzle schema to incorporate new changes * add pothos plugin for implementing relay graphql connections * add some common graphql connection utitlies * add graphql implementation for organization read/write functionalities added graphql mutation resolvers for creating, reading, updating and deleting an organization * fix some stuff in user graphql implementation extracted out `updatedAt` field as its own resolver for better access control * fix user tests according to new changes * update gql tada introspection result * delete tag folders table tags can be used as folders for tags, so there is no requirement for a seperate table for managing tag folders * add utility function added a utility function that takes in a javascript object and a list of key paths within that object as arguments and outputs all paths among those key paths that correspond to a non-undefined value * add graphql implementation for organization membership added graphql implementation for handling read/write operations for an organization membership added graphql implementation for handling read operations for relations between organization membership, organization and user * add utility for checking if a value is null or undefined * add graphql implementation for tag added graphql implementation for handling read/write operations for a tag added graphql implementation for handling read operations for relations between tag and organization * add advertisement graphql implementation added graphql implementation for handling read/write operations for an advertisement added graphql implementation for handling read operations for relations between advertisement and organization * fixed some error messages * update drizzle schema * add post graphql implementation added graphql implementation for handling read/write operations for a post added graphql implementation for handling read operations for relations between post and organization * fixed imports for some modules * update node, pnpm version and dependencies * add post vote graphql implementation added graphql implementation for handling read/write operations for a post vote added graphql implementation for handling read operations for relations between post and post voters * update drizzle schema * add comment graphql implementation added graphql implementation for handling read/write operations for a post comment added graphql implementation for handling read operations for relations between comment and post * add comment vote graphql implementation added graphql implementation for handling read/write operations for a comment vote added graphql implementation for handling read operations for relations between comment and comment voters --- docker/api.Containerfile | 2 +- ...ql => 20241205130831_cynical_meltdown.sql} | 226 +-- ...shot.json => 20241205130831_snapshot.json} | 898 ++--------- drizzle_migrations/meta/_journal.json | 4 +- package.json | 35 +- pnpm-lock.yaml | 1376 +++++++++-------- schema.graphql | 135 +- scripts/generateGraphQLSDLFile.ts | 4 +- .../enums/organizationMembershipRole.ts | 2 +- src/drizzle/enums/userRole.ts | 2 +- src/drizzle/schema.ts | 1 - src/drizzle/tables/actionCategories.ts | 6 +- src/drizzle/tables/actions.ts | 6 +- .../tables/advertisementAttachments.ts | 66 +- src/drizzle/tables/advertisements.ts | 89 +- src/drizzle/tables/agendaItems.ts | 8 +- src/drizzle/tables/agendaSections.ts | 10 +- src/drizzle/tables/commentVotes.ts | 80 +- src/drizzle/tables/comments.ts | 117 +- src/drizzle/tables/eventAttachments.ts | 19 +- src/drizzle/tables/eventAttendances.ts | 6 +- src/drizzle/tables/events.ts | 6 +- src/drizzle/tables/families.ts | 6 +- src/drizzle/tables/familyMemberships.ts | 6 +- src/drizzle/tables/fundraisingCampaigns.ts | 6 +- src/drizzle/tables/funds.ts | 6 +- src/drizzle/tables/organizationMemberships.ts | 50 +- src/drizzle/tables/organizations.ts | 99 +- src/drizzle/tables/pledges.ts | 6 +- src/drizzle/tables/postAttachments.ts | 65 +- src/drizzle/tables/postVotes.ts | 81 +- src/drizzle/tables/posts.ts | 99 +- src/drizzle/tables/recurrences.ts | 6 +- src/drizzle/tables/tagAssignments.ts | 79 +- src/drizzle/tables/tagFolders.ts | 95 -- src/drizzle/tables/tags.ts | 116 +- src/drizzle/tables/users.ts | 315 ++-- src/drizzle/tables/venueAttachments.ts | 19 +- src/drizzle/tables/venueBookings.ts | 6 +- src/drizzle/tables/venues.ts | 6 +- .../tables/volunteerGroupAssignments.ts | 6 +- src/drizzle/tables/volunteerGroups.ts | 6 +- src/graphql/builder.ts | 6 +- .../enums/AdvertisementAttachmentType.ts | 10 + src/graphql/enums/AdvertisementType.ts | 7 + src/graphql/enums/CommentVoteType.ts | 7 + .../enums/OrganizationMembershipRole.ts | 10 + src/graphql/enums/PostAttachmentType.ts | 7 + src/graphql/enums/PostVoteType.ts | 7 + src/graphql/enums/index.ts | 6 + .../CreateAdvertisementAttachmentInput.ts | 29 + .../inputs/CreatePostAttachmentInput.ts | 29 + .../MutationCreateAdvertisementInput.ts | 75 + .../inputs/MutationCreateCommentInput.ts | 27 + .../inputs/MutationCreateCommentVoteInput.ts | 29 + .../inputs/MutationCreateOrganizationInput.ts | 50 + ...tationCreateOrganizationMembershipInput.ts | 36 + src/graphql/inputs/MutationCreatePostInput.ts | 47 + .../inputs/MutationCreatePostVoteInput.ts | 29 + src/graphql/inputs/MutationCreateTagInput.ts | 36 + .../MutationDeleteAdvertisementInput.ts | 21 + .../inputs/MutationDeleteCommentInput.ts | 21 + .../inputs/MutationDeleteCommentVoteInput.ts | 32 + .../inputs/MutationDeleteOrganizationInput.ts | 21 + ...tationDeleteOrganizationMembershipInput.ts | 27 + src/graphql/inputs/MutationDeletePostInput.ts | 21 + .../inputs/MutationDeletePostVoteInput.ts | 29 + src/graphql/inputs/MutationDeleteTagInput.ts | 21 + .../MutationUpdateAdvertisementInput.ts | 69 + .../inputs/MutationUpdateCommentInput.ts | 33 + .../inputs/MutationUpdateCommentVoteInput.ts | 29 + .../inputs/MutationUpdateOrganizationInput.ts | 66 + ...tationUpdateOrganizationMembershipInput.ts | 43 + src/graphql/inputs/MutationUpdatePostInput.ts | 37 + .../inputs/MutationUpdatePostVoteInput.ts | 29 + src/graphql/inputs/MutationUpdateTagInput.ts | 39 + src/graphql/inputs/QueryAdvertisementInput.ts | 21 + src/graphql/inputs/QueryCommentInput.ts | 19 + src/graphql/inputs/QueryOrganizationInput.ts | 21 + src/graphql/inputs/QueryPostInput.ts | 19 + src/graphql/inputs/QueryTagInput.ts | 19 + src/graphql/inputs/index.ts | 31 + .../types/Advertisement/Advertisement.ts | 45 + src/graphql/types/Advertisement/createdAt.ts | 66 + src/graphql/types/Advertisement/creator.ts | 93 ++ src/graphql/types/Advertisement/index.ts | 6 + .../types/Advertisement/organization.ts | 35 + src/graphql/types/Advertisement/updatedAt.ts | 66 + src/graphql/types/Advertisement/updater.ts | 93 ++ .../AdvertisementAttachment.ts | 22 + .../types/AdvertisementAttachment/index.ts | 1 + src/graphql/types/Comment/Comment.ts | 27 + src/graphql/types/Comment/creator.ts | 40 + src/graphql/types/Comment/downVoters.ts | 225 +++ src/graphql/types/Comment/downVotesCount.ts | 31 + src/graphql/types/Comment/index.ts | 8 + src/graphql/types/Comment/post.ts | 41 + src/graphql/types/Comment/upVoters.ts | 225 +++ src/graphql/types/Comment/upVotesCount.ts | 31 + src/graphql/types/Comment/updater.ts | 119 ++ .../types/Mutation/createAdvertisement.ts | 208 +++ src/graphql/types/Mutation/createComment.ts | 154 ++ .../types/Mutation/createCommentVote.ts | 183 +++ .../types/Mutation/createOrganization.ts | 135 ++ .../Mutation/createOrganizationMembership.ts | 220 +++ src/graphql/types/Mutation/createPost.ts | 197 +++ src/graphql/types/Mutation/createPostVote.ts | 179 +++ src/graphql/types/Mutation/createTag.ts | 243 +++ .../types/Mutation/deleteAdvertisement.ts | 154 ++ src/graphql/types/Mutation/deleteComment.ts | 158 ++ .../types/Mutation/deleteCommentVote.ts | 226 +++ .../types/Mutation/deleteOrganization.ts | 106 ++ .../Mutation/deleteOrganizationMembership.ts | 230 +++ src/graphql/types/Mutation/deletePost.ts | 156 ++ src/graphql/types/Mutation/deletePostVote.ts | 268 ++++ src/graphql/types/Mutation/deleteTag.ts | 144 ++ src/graphql/types/Mutation/index.ts | 21 + src/graphql/types/Mutation/signUp.ts | 2 +- .../types/Mutation/updateAdvertisement.ts | 237 +++ src/graphql/types/Mutation/updateComment.ts | 161 ++ .../types/Mutation/updateCommentVote.ts | 172 +++ .../types/Mutation/updateCurrentUser.ts | 2 +- .../types/Mutation/updateOrganization.ts | 117 ++ .../Mutation/updateOrganizationMembership.ts | 234 +++ src/graphql/types/Mutation/updatePost.ts | 243 +++ src/graphql/types/Mutation/updatePostVote.ts | 166 ++ src/graphql/types/Mutation/updateTag.ts | 247 +++ src/graphql/types/Mutation/updateUser.ts | 2 +- .../types/Organization/Organization.ts | 47 + .../types/Organization/advertisements.ts | 225 +++ src/graphql/types/Organization/creator.ts | 48 + src/graphql/types/Organization/index.ts | 11 + src/graphql/types/Organization/members.ts | 229 +++ src/graphql/types/Organization/pinnedPosts.ts | 256 +++ .../types/Organization/pinnedPostsCount.ts | 31 + src/graphql/types/Organization/posts.ts | 219 +++ src/graphql/types/Organization/postsCount.ts | 26 + src/graphql/types/Organization/tags.ts | 228 +++ src/graphql/types/Organization/updatedAt.ts | 63 + src/graphql/types/Organization/updater.ts | 94 ++ src/graphql/types/Post/Post.ts | 41 + src/graphql/types/Post/comments.ts | 176 +++ src/graphql/types/Post/commentsCount.ts | 26 + src/graphql/types/Post/creator.ts | 40 + src/graphql/types/Post/downVoters.ts | 219 +++ src/graphql/types/Post/downVotesCount.ts | 31 + src/graphql/types/Post/index.ts | 10 + src/graphql/types/Post/organization.ts | 35 + src/graphql/types/Post/upVoters.ts | 219 +++ src/graphql/types/Post/upVotesCount.ts | 31 + src/graphql/types/Post/updater.ts | 93 ++ .../types/PostAttachment/PostAttachment.ts | 21 + src/graphql/types/PostAttachment/index.ts | 1 + src/graphql/types/Query/advertisement.ts | 133 ++ src/graphql/types/Query/comment.ts | 134 ++ src/graphql/types/Query/index.ts | 5 + src/graphql/types/Query/organization.ts | 68 + src/graphql/types/Query/post.ts | 131 ++ src/graphql/types/Query/tag.ts | 126 ++ src/graphql/types/README.md | 10 +- src/graphql/types/Tag/Tag.ts | 22 + src/graphql/types/Tag/createdAt.ts | 66 + src/graphql/types/Tag/creator.ts | 93 ++ src/graphql/types/Tag/index.ts | 8 + src/graphql/types/Tag/organization.ts | 35 + src/graphql/types/Tag/parentTag.ts | 87 ++ src/graphql/types/Tag/tagsWhereParentTag.ts | 228 +++ src/graphql/types/Tag/updatedAt.ts | 66 + src/graphql/types/Tag/updater.ts | 93 ++ src/graphql/types/User/User.ts | 8 +- src/graphql/types/User/creator.ts | 26 +- src/graphql/types/User/index.ts | 2 + .../types/User/organizationsWhereMember.ts | 223 +++ src/graphql/types/User/updatedAt.ts | 53 + src/graphql/types/User/updater.ts | 20 +- src/graphql/types/index.ts | 7 + src/utilities/defaultGraphQLConnection.ts | 256 +++ .../getKeyPathsWithNonUndefinedValues.ts | 33 + src/utilities/isNotNullish.ts | 15 + src/utilities/talawaGraphQLError.ts | 27 +- .../graphql/Mutation/createUser.test.ts | 29 +- .../Mutation/deleteCurrentUser.test.ts | 4 +- .../graphql/Mutation/deleteUser.test.ts | 10 +- test/routes/graphql/Mutation/signUp.test.ts | 9 +- .../Mutation/updateCurrentUser.test.ts | 23 +- .../graphql/Mutation/updateUser.test.ts | 23 +- test/routes/graphql/Query/currentUser.test.ts | 2 +- .../Query/renewAuthenticationToken.test.ts | 2 +- test/routes/graphql/Query/user.test.ts | 2 +- test/routes/graphql/User/creator.test.ts | 184 ++- test/routes/graphql/User/updatedAt.test.ts | 352 +++++ test/routes/graphql/User/updater.test.ts | 6 +- test/routes/graphql/documentNodes.ts | 18 +- test/routes/graphql/gql.tada-cache.d.ts | 28 +- test/routes/graphql/gql.tada.d.ts | 11 +- 195 files changed, 13811 insertions(+), 2317 deletions(-) rename drizzle_migrations/{20241118154748_bizarre_steve_rogers.sql => 20241205130831_cynical_meltdown.sql} (86%) rename drizzle_migrations/meta/{20241118154748_snapshot.json => 20241205130831_snapshot.json} (89%) delete mode 100755 src/drizzle/tables/tagFolders.ts create mode 100755 src/graphql/enums/AdvertisementAttachmentType.ts create mode 100755 src/graphql/enums/AdvertisementType.ts create mode 100755 src/graphql/enums/CommentVoteType.ts create mode 100755 src/graphql/enums/OrganizationMembershipRole.ts create mode 100755 src/graphql/enums/PostAttachmentType.ts create mode 100755 src/graphql/enums/PostVoteType.ts create mode 100644 src/graphql/inputs/CreateAdvertisementAttachmentInput.ts create mode 100644 src/graphql/inputs/CreatePostAttachmentInput.ts create mode 100644 src/graphql/inputs/MutationCreateAdvertisementInput.ts create mode 100644 src/graphql/inputs/MutationCreateCommentInput.ts create mode 100644 src/graphql/inputs/MutationCreateCommentVoteInput.ts create mode 100644 src/graphql/inputs/MutationCreateOrganizationInput.ts create mode 100644 src/graphql/inputs/MutationCreateOrganizationMembershipInput.ts create mode 100644 src/graphql/inputs/MutationCreatePostInput.ts create mode 100644 src/graphql/inputs/MutationCreatePostVoteInput.ts create mode 100644 src/graphql/inputs/MutationCreateTagInput.ts create mode 100644 src/graphql/inputs/MutationDeleteAdvertisementInput.ts create mode 100644 src/graphql/inputs/MutationDeleteCommentInput.ts create mode 100644 src/graphql/inputs/MutationDeleteCommentVoteInput.ts create mode 100644 src/graphql/inputs/MutationDeleteOrganizationInput.ts create mode 100644 src/graphql/inputs/MutationDeleteOrganizationMembershipInput.ts create mode 100644 src/graphql/inputs/MutationDeletePostInput.ts create mode 100644 src/graphql/inputs/MutationDeletePostVoteInput.ts create mode 100644 src/graphql/inputs/MutationDeleteTagInput.ts create mode 100644 src/graphql/inputs/MutationUpdateAdvertisementInput.ts create mode 100644 src/graphql/inputs/MutationUpdateCommentInput.ts create mode 100644 src/graphql/inputs/MutationUpdateCommentVoteInput.ts create mode 100644 src/graphql/inputs/MutationUpdateOrganizationInput.ts create mode 100644 src/graphql/inputs/MutationUpdateOrganizationMembershipInput.ts create mode 100644 src/graphql/inputs/MutationUpdatePostInput.ts create mode 100644 src/graphql/inputs/MutationUpdatePostVoteInput.ts create mode 100644 src/graphql/inputs/MutationUpdateTagInput.ts create mode 100644 src/graphql/inputs/QueryAdvertisementInput.ts create mode 100644 src/graphql/inputs/QueryCommentInput.ts create mode 100644 src/graphql/inputs/QueryOrganizationInput.ts create mode 100644 src/graphql/inputs/QueryPostInput.ts create mode 100644 src/graphql/inputs/QueryTagInput.ts create mode 100644 src/graphql/types/Advertisement/Advertisement.ts create mode 100644 src/graphql/types/Advertisement/createdAt.ts create mode 100644 src/graphql/types/Advertisement/creator.ts create mode 100644 src/graphql/types/Advertisement/index.ts create mode 100644 src/graphql/types/Advertisement/organization.ts create mode 100644 src/graphql/types/Advertisement/updatedAt.ts create mode 100644 src/graphql/types/Advertisement/updater.ts create mode 100644 src/graphql/types/AdvertisementAttachment/AdvertisementAttachment.ts create mode 100644 src/graphql/types/AdvertisementAttachment/index.ts create mode 100644 src/graphql/types/Comment/Comment.ts create mode 100644 src/graphql/types/Comment/creator.ts create mode 100644 src/graphql/types/Comment/downVoters.ts create mode 100644 src/graphql/types/Comment/downVotesCount.ts create mode 100644 src/graphql/types/Comment/index.ts create mode 100644 src/graphql/types/Comment/post.ts create mode 100644 src/graphql/types/Comment/upVoters.ts create mode 100644 src/graphql/types/Comment/upVotesCount.ts create mode 100644 src/graphql/types/Comment/updater.ts create mode 100644 src/graphql/types/Mutation/createAdvertisement.ts create mode 100644 src/graphql/types/Mutation/createComment.ts create mode 100644 src/graphql/types/Mutation/createCommentVote.ts create mode 100644 src/graphql/types/Mutation/createOrganization.ts create mode 100644 src/graphql/types/Mutation/createOrganizationMembership.ts create mode 100644 src/graphql/types/Mutation/createPost.ts create mode 100644 src/graphql/types/Mutation/createPostVote.ts create mode 100644 src/graphql/types/Mutation/createTag.ts create mode 100644 src/graphql/types/Mutation/deleteAdvertisement.ts create mode 100644 src/graphql/types/Mutation/deleteComment.ts create mode 100644 src/graphql/types/Mutation/deleteCommentVote.ts create mode 100644 src/graphql/types/Mutation/deleteOrganization.ts create mode 100644 src/graphql/types/Mutation/deleteOrganizationMembership.ts create mode 100644 src/graphql/types/Mutation/deletePost.ts create mode 100644 src/graphql/types/Mutation/deletePostVote.ts create mode 100644 src/graphql/types/Mutation/deleteTag.ts create mode 100644 src/graphql/types/Mutation/updateAdvertisement.ts create mode 100644 src/graphql/types/Mutation/updateComment.ts create mode 100644 src/graphql/types/Mutation/updateCommentVote.ts create mode 100644 src/graphql/types/Mutation/updateOrganization.ts create mode 100644 src/graphql/types/Mutation/updateOrganizationMembership.ts create mode 100644 src/graphql/types/Mutation/updatePost.ts create mode 100644 src/graphql/types/Mutation/updatePostVote.ts create mode 100644 src/graphql/types/Mutation/updateTag.ts create mode 100644 src/graphql/types/Organization/Organization.ts create mode 100644 src/graphql/types/Organization/advertisements.ts create mode 100755 src/graphql/types/Organization/creator.ts create mode 100644 src/graphql/types/Organization/index.ts create mode 100644 src/graphql/types/Organization/members.ts create mode 100644 src/graphql/types/Organization/pinnedPosts.ts create mode 100644 src/graphql/types/Organization/pinnedPostsCount.ts create mode 100644 src/graphql/types/Organization/posts.ts create mode 100644 src/graphql/types/Organization/postsCount.ts create mode 100644 src/graphql/types/Organization/tags.ts create mode 100644 src/graphql/types/Organization/updatedAt.ts create mode 100755 src/graphql/types/Organization/updater.ts create mode 100644 src/graphql/types/Post/Post.ts create mode 100644 src/graphql/types/Post/comments.ts create mode 100644 src/graphql/types/Post/commentsCount.ts create mode 100644 src/graphql/types/Post/creator.ts create mode 100644 src/graphql/types/Post/downVoters.ts create mode 100644 src/graphql/types/Post/downVotesCount.ts create mode 100644 src/graphql/types/Post/index.ts create mode 100644 src/graphql/types/Post/organization.ts create mode 100644 src/graphql/types/Post/upVoters.ts create mode 100644 src/graphql/types/Post/upVotesCount.ts create mode 100644 src/graphql/types/Post/updater.ts create mode 100644 src/graphql/types/PostAttachment/PostAttachment.ts create mode 100644 src/graphql/types/PostAttachment/index.ts create mode 100644 src/graphql/types/Query/advertisement.ts create mode 100644 src/graphql/types/Query/comment.ts create mode 100644 src/graphql/types/Query/organization.ts create mode 100644 src/graphql/types/Query/post.ts create mode 100644 src/graphql/types/Query/tag.ts create mode 100644 src/graphql/types/Tag/Tag.ts create mode 100644 src/graphql/types/Tag/createdAt.ts create mode 100644 src/graphql/types/Tag/creator.ts create mode 100644 src/graphql/types/Tag/index.ts create mode 100644 src/graphql/types/Tag/organization.ts create mode 100644 src/graphql/types/Tag/parentTag.ts create mode 100644 src/graphql/types/Tag/tagsWhereParentTag.ts create mode 100644 src/graphql/types/Tag/updatedAt.ts create mode 100644 src/graphql/types/Tag/updater.ts create mode 100644 src/graphql/types/User/organizationsWhereMember.ts create mode 100644 src/graphql/types/User/updatedAt.ts create mode 100755 src/utilities/defaultGraphQLConnection.ts create mode 100644 src/utilities/getKeyPathsWithNonUndefinedValues.ts create mode 100644 src/utilities/isNotNullish.ts create mode 100644 test/routes/graphql/User/updatedAt.test.ts diff --git a/docker/api.Containerfile b/docker/api.Containerfile index 249a026942..d4e13b5c1d 100644 --- a/docker/api.Containerfile +++ b/docker/api.Containerfile @@ -37,7 +37,7 @@ RUN curl -fsSL https://fnm.vercel.app/install | bash -s -- --skip-shell \ WORKDIR /home/talawa/api # This build stage sets up and switches to the `talawa` non root user, sets up pnpm configuration and checks out into the `/home/talawa/api` directory as the working directory. -FROM node:22.11.0-bookworm-slim AS base +FROM node:22.12.0-bookworm-slim AS base # Used to configure the group id for the group assigned to the non-root "talawa" user within the image. ARG API_GID # Used to configure the user id for the non-root "talawa" user within the image. diff --git a/drizzle_migrations/20241118154748_bizarre_steve_rogers.sql b/drizzle_migrations/20241205130831_cynical_meltdown.sql similarity index 86% rename from drizzle_migrations/20241118154748_bizarre_steve_rogers.sql rename to drizzle_migrations/20241205130831_cynical_meltdown.sql index 596772f3e5..5b7352aea4 100644 --- a/drizzle_migrations/20241118154748_bizarre_steve_rogers.sql +++ b/drizzle_migrations/20241205130831_cynical_meltdown.sql @@ -6,7 +6,7 @@ CREATE TYPE "public"."event_attachment_type" AS ENUM('image', 'video');--> state CREATE TYPE "public"."event_attendee_registration_invite_status" AS ENUM('accepted', 'declined', 'no_response');--> statement-breakpoint CREATE TYPE "public"."family_membership_role" AS ENUM('adult', 'child', 'head_of_household', 'spouse');--> statement-breakpoint CREATE TYPE "public"."iso_3166_alpha_2_country_code" AS ENUM('ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'ao', 'aq', 'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bl', 'bm', 'bn', 'bo', 'bq', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cu', 'cv', 'cw', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mf', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss', 'st', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'za', 'zm', 'zw');--> statement-breakpoint -CREATE TYPE "public"."organization_membership_role" AS ENUM('administrator', 'base');--> statement-breakpoint +CREATE TYPE "public"."organization_membership_role" AS ENUM('administrator', 'regular');--> statement-breakpoint CREATE TYPE "public"."post_attachment_type" AS ENUM('image', 'video');--> statement-breakpoint CREATE TYPE "public"."post_vote_type" AS ENUM('down_vote', 'up_vote');--> statement-breakpoint CREATE TYPE "public"."recurrence_type" AS ENUM('daily', 'monthly', 'weekly', 'yearly');--> statement-breakpoint @@ -14,7 +14,7 @@ CREATE TYPE "public"."user_education_grade" AS ENUM('grade_1', 'grade_2', 'grade CREATE TYPE "public"."user_employment_status" AS ENUM('full_time', 'part_time', 'unemployed');--> statement-breakpoint CREATE TYPE "public"."user_marital_status" AS ENUM('divorced', 'engaged', 'married', 'seperated', 'single', 'widowed');--> statement-breakpoint CREATE TYPE "public"."user_natal_sex" AS ENUM('female', 'intersex', 'male');--> statement-breakpoint -CREATE TYPE "public"."user_role" AS ENUM('administrator', 'base');--> statement-breakpoint +CREATE TYPE "public"."user_role" AS ENUM('administrator', 'regular');--> statement-breakpoint CREATE TYPE "public"."venue_attachment_type" AS ENUM('image', 'video');--> statement-breakpoint CREATE TYPE "public"."volunteer_group_assignment_invite_status" AS ENUM('accepted', 'declined', 'no_response');--> statement-breakpoint CREATE TABLE IF NOT EXISTS "action_categories" ( @@ -50,16 +50,15 @@ CREATE TABLE IF NOT EXISTS "advertisement_attachments" ( "advertisement_id" uuid NOT NULL, "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, "creator_id" uuid NOT NULL, - "position" integer NOT NULL, + "type" "advertisement_attachment_type" NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid, - "uri" text NOT NULL, - "type" "advertisement_attachment_type" NOT NULL + "uri" text NOT NULL ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "advertisements" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, "description" text, "end_at" timestamp (3) with time zone NOT NULL, "id" uuid PRIMARY KEY NOT NULL, @@ -94,7 +93,6 @@ CREATE TABLE IF NOT EXISTS "agenda_sections" ( "id" uuid PRIMARY KEY NOT NULL, "name" text NOT NULL, "parent_section_id" uuid, - "position" integer NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid ); @@ -102,23 +100,18 @@ CREATE TABLE IF NOT EXISTS "agenda_sections" ( CREATE TABLE IF NOT EXISTS "comment_votes" ( "comment_id" uuid NOT NULL, "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, + "id" uuid PRIMARY KEY NOT NULL, "type" "comment_vote_type" NOT NULL, "updated_at" timestamp (3) with time zone, - "updated_id" uuid, - "voter_id" uuid, - CONSTRAINT "comment_votes_comment_id_voter_id_pk" PRIMARY KEY("comment_id","voter_id") + "updated_id" uuid ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "comments" ( "body" text NOT NULL, - "commenter_id" uuid, "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, "id" uuid PRIMARY KEY NOT NULL, - "parent_comment_id" uuid, - "pinned_at" timestamp (3) with time zone, - "pinner_id" uuid, "post_id" uuid NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid @@ -128,7 +121,6 @@ CREATE TABLE IF NOT EXISTS "event_attachments" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, "creator_id" uuid NOT NULL, "event_id" uuid NOT NULL, - "position" integer NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid, "uri" text NOT NULL, @@ -220,8 +212,7 @@ CREATE TABLE IF NOT EXISTS "funds" ( --> statement-breakpoint CREATE TABLE IF NOT EXISTS "organization_memberships" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, - "is_approved" boolean NOT NULL, + "creator_id" uuid, "member_id" uuid NOT NULL, "organization_id" uuid NOT NULL, "role" "organization_membership_role" NOT NULL, @@ -236,10 +227,9 @@ CREATE TABLE IF NOT EXISTS "organizations" ( "city" text, "country_code" "iso_3166_alpha_2_country_code", "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, "description" text, "id" uuid PRIMARY KEY NOT NULL, - "is_private" boolean NOT NULL, "name" text NOT NULL, "postal_code" text, "state" text, @@ -266,7 +256,6 @@ CREATE TABLE IF NOT EXISTS "pledges" ( CREATE TABLE IF NOT EXISTS "post_attachments" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, "creator_id" uuid NOT NULL, - "position" integer NOT NULL, "post_id" uuid NOT NULL, "type" "post_attachment_type" NOT NULL, "updated_at" timestamp (3) with time zone, @@ -276,13 +265,12 @@ CREATE TABLE IF NOT EXISTS "post_attachments" ( --> statement-breakpoint CREATE TABLE IF NOT EXISTS "post_votes" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, + "id" uuid PRIMARY KEY NOT NULL, "post_id" uuid NOT NULL, - "updated_at" timestamp (3) with time zone, - "updated_id" uuid, "type" "post_vote_type" NOT NULL, - "voter_id" uuid, - CONSTRAINT "post_votes_post_id_voter_id_pk" PRIMARY KEY("post_id","voter_id") + "updated_at" timestamp (3) with time zone, + "updated_id" uuid ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "posts" ( @@ -292,8 +280,6 @@ CREATE TABLE IF NOT EXISTS "posts" ( "id" uuid PRIMARY KEY NOT NULL, "organization_id" uuid NOT NULL, "pinned_at" timestamp (3) with time zone, - "pinner_id" uuid, - "poster_id" uuid, "updated_at" timestamp (3) with time zone, "updater_id" uuid ); @@ -318,31 +304,21 @@ CREATE TABLE IF NOT EXISTS "recurrences" ( CREATE TABLE IF NOT EXISTS "tag_assignments" ( "assignee_id" uuid NOT NULL, "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, - "tag_id" uuid, + "creator_id" uuid, + "tag_id" uuid NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid, CONSTRAINT "tag_assignments_assignee_id_tag_id_pk" PRIMARY KEY("assignee_id","tag_id") ); --> statement-breakpoint -CREATE TABLE IF NOT EXISTS "tag_folders" ( - "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, - "id" uuid PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "organization_id" uuid NOT NULL, - "parent_folder_id" uuid, - "updated_at" timestamp (3) with time zone, - "updater_id" uuid -); ---> statement-breakpoint CREATE TABLE IF NOT EXISTS "tags" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, - "folder_id" uuid, + "creator_id" uuid, "id" uuid PRIMARY KEY NOT NULL, + "is_folder" boolean NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, + "parent_tag_id" uuid, "updated_at" timestamp (3) with time zone, "updater_id" uuid ); @@ -354,7 +330,7 @@ CREATE TABLE IF NOT EXISTS "users" ( "city" text, "country_code" "iso_3166_alpha_2_country_code", "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, - "creator_id" uuid NOT NULL, + "creator_id" uuid, "description" text, "education_grade" "user_education_grade", "email_address" text NOT NULL, @@ -379,7 +355,6 @@ CREATE TABLE IF NOT EXISTS "users" ( CREATE TABLE IF NOT EXISTS "venue_attachments" ( "created_at" timestamp (3) with time zone DEFAULT now() NOT NULL, "creator_id" uuid NOT NULL, - "position" integer NOT NULL, "type" "venue_attachment_type" NOT NULL, "updated_at" timestamp (3) with time zone, "updater_id" uuid, @@ -505,19 +480,19 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "advertisements" ADD CONSTRAINT "advertisements_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; @@ -571,61 +546,37 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_comment_id_comments_id_fk" FOREIGN KEY ("comment_id") REFERENCES "public"."comments"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_updated_id_users_id_fk" FOREIGN KEY ("updated_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_voter_id_users_id_fk" FOREIGN KEY ("voter_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_comment_id_comments_id_fk" FOREIGN KEY ("comment_id") REFERENCES "public"."comments"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_commenter_id_users_id_fk" FOREIGN KEY ("commenter_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comment_votes" ADD CONSTRAINT "comment_votes_updated_id_users_id_fk" FOREIGN KEY ("updated_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_parent_comment_id_comments_id_fk" FOREIGN KEY ("parent_comment_id") REFERENCES "public"."comments"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comments" ADD CONSTRAINT "comments_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_pinner_id_users_id_fk" FOREIGN KEY ("pinner_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comments" ADD CONSTRAINT "comments_post_id_posts_id_fk" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_post_id_posts_id_fk" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "comments" ADD CONSTRAINT "comments_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "comments" ADD CONSTRAINT "comments_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; @@ -775,37 +726,37 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_member_id_users_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_member_id_users_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organization_memberships" ADD CONSTRAINT "organization_memberships_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organizations" ADD CONSTRAINT "organizations_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organizations" ADD CONSTRAINT "organizations_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "organizations" ADD CONSTRAINT "organizations_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "organizations" ADD CONSTRAINT "organizations_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; @@ -853,55 +804,37 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_post_id_posts_id_fk" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_updated_id_users_id_fk" FOREIGN KEY ("updated_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_voter_id_users_id_fk" FOREIGN KEY ("voter_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "posts" ADD CONSTRAINT "posts_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_post_id_posts_id_fk" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "posts" ADD CONSTRAINT "posts_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "post_votes" ADD CONSTRAINT "post_votes_updated_id_users_id_fk" FOREIGN KEY ("updated_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "posts" ADD CONSTRAINT "posts_pinner_id_users_id_fk" FOREIGN KEY ("pinner_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "posts" ADD CONSTRAINT "posts_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "posts" ADD CONSTRAINT "posts_poster_id_users_id_fk" FOREIGN KEY ("poster_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "posts" ADD CONSTRAINT "posts_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "posts" ADD CONSTRAINT "posts_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "posts" ADD CONSTRAINT "posts_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; @@ -925,85 +858,61 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_assignee_id_users_id_fk" FOREIGN KEY ("assignee_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_assignee_id_users_id_fk" FOREIGN KEY ("assignee_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tag_assignments" ADD CONSTRAINT "tag_assignments_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_folders" ADD CONSTRAINT "tag_folders_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tags" ADD CONSTRAINT "tags_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_folders" ADD CONSTRAINT "tag_folders_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tags" ADD CONSTRAINT "tags_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_folders" ADD CONSTRAINT "tag_folders_parent_folder_id_tag_folders_id_fk" FOREIGN KEY ("parent_folder_id") REFERENCES "public"."tag_folders"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tags" ADD CONSTRAINT "tags_parent_tag_id_tags_id_fk" FOREIGN KEY ("parent_tag_id") REFERENCES "public"."tags"("id") ON DELETE cascade ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tag_folders" ADD CONSTRAINT "tag_folders_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "tags" ADD CONSTRAINT "tags_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tags" ADD CONSTRAINT "tags_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "users" ADD CONSTRAINT "users_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "tags" ADD CONSTRAINT "tags_folder_id_tag_folders_id_fk" FOREIGN KEY ("folder_id") REFERENCES "public"."tag_folders"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "tags" ADD CONSTRAINT "tags_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "tags" ADD CONSTRAINT "tags_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "users" ADD CONSTRAINT "users_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "users" ADD CONSTRAINT "users_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "users" ADD CONSTRAINT "users_updater_id_users_id_fk" FOREIGN KEY ("updater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE cascade; EXCEPTION WHEN duplicate_object THEN null; END $$; @@ -1130,8 +1039,6 @@ CREATE INDEX IF NOT EXISTS "actions_organization_id_index" ON "actions" USING bt CREATE INDEX IF NOT EXISTS "advertisement_attachments_advertisement_id_index" ON "advertisement_attachments" USING btree ("advertisement_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "advertisement_attachments_created_at_index" ON "advertisement_attachments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "advertisement_attachments_creator_id_index" ON "advertisement_attachments" USING btree ("creator_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "advertisement_attachments_advertisement_id_position_index" ON "advertisement_attachments" USING btree ("advertisement_id","position");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "advertisements_created_at_index" ON "advertisements" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "advertisements_creator_id_index" ON "advertisements" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "advertisements_end_at_index" ON "advertisements" USING btree ("end_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "advertisements_name_index" ON "advertisements" USING btree ("name");--> statement-breakpoint @@ -1152,22 +1059,16 @@ CREATE INDEX IF NOT EXISTS "agenda_sections_event_id_index" ON "agenda_sections" CREATE INDEX IF NOT EXISTS "agenda_sections_name_index" ON "agenda_sections" USING btree ("name");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "agenda_sections_parent_section_id_index" ON "agenda_sections" USING btree ("parent_section_id");--> statement-breakpoint CREATE UNIQUE INDEX IF NOT EXISTS "agenda_sections_event_id_name_index" ON "agenda_sections" USING btree ("event_id","name");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "agenda_sections_event_id_position_index" ON "agenda_sections" USING btree ("event_id","position");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comment_votes_comment_id_index" ON "comment_votes" USING btree ("comment_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "comment_votes_created_at_index" ON "comment_votes" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comment_votes_creator_id_index" ON "comment_votes" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comment_votes_type_index" ON "comment_votes" USING btree ("type");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "comment_votes_voter_id_index" ON "comment_votes" USING btree ("voter_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "comments_commenter_id_index" ON "comments" USING btree ("commenter_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "comment_votes_comment_id_creator_id_index" ON "comment_votes" USING btree ("comment_id","creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comments_created_at_index" ON "comments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comments_creator_id_index" ON "comments" USING btree ("creator_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "comments_parent_comment_id_index" ON "comments" USING btree ("parent_comment_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "comments_pinned_at_index" ON "comments" USING btree ("pinned_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "comments_post_id_index" ON "comments" USING btree ("post_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attachments_event_id_index" ON "event_attachments" USING btree ("event_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attachments_created_at_index" ON "event_attachments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attachments_creator_id_index" ON "event_attachments" USING btree ("creator_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "event_attachments_event_id_position_index" ON "event_attachments" USING btree ("event_id","position");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attendances_attendee_id_index" ON "event_attendances" USING btree ("attendee_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attendances_check_in_at_index" ON "event_attendances" USING btree ("check_in_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "event_attendances_check_out_at_index" ON "event_attendances" USING btree ("check_out_at");--> statement-breakpoint @@ -1200,9 +1101,9 @@ CREATE INDEX IF NOT EXISTS "funds_name_index" ON "funds" USING btree ("name");-- CREATE INDEX IF NOT EXISTS "funds_organization_id_index" ON "funds" USING btree ("organization_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organization_memberships_created_at_index" ON "organization_memberships" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organization_memberships_creator_id_index" ON "organization_memberships" USING btree ("creator_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "organization_memberships_is_approved_index" ON "organization_memberships" USING btree ("is_approved");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organization_memberships_member_id_index" ON "organization_memberships" USING btree ("member_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organization_memberships_organization_id_index" ON "organization_memberships" USING btree ("organization_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "organization_memberships_role_index" ON "organization_memberships" USING btree ("role");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organizations_creator_id_index" ON "organizations" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organizations_name_index" ON "organizations" USING btree ("name");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "organizations_updater_id_index" ON "organizations" USING btree ("updater_id");--> statement-breakpoint @@ -1215,17 +1116,14 @@ CREATE INDEX IF NOT EXISTS "pledges_start_at_index" ON "pledges" USING btree ("s CREATE INDEX IF NOT EXISTS "post_attachments_created_at_index" ON "post_attachments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "post_attachments_creator_id_index" ON "post_attachments" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "post_attachments_post_id_index" ON "post_attachments" USING btree ("post_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "post_attachments_position_post_id_index" ON "post_attachments" USING btree ("position","post_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "post_votes_created_at_index" ON "post_votes" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "post_votes_creator_id_index" ON "post_votes" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "post_votes_post_id_index" ON "post_votes" USING btree ("post_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "post_votes_type_index" ON "post_votes" USING btree ("type");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "post_votes_voter_id_index" ON "post_votes" USING btree ("voter_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "post_votes_creator_id_post_id_index" ON "post_votes" USING btree ("creator_id","post_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "posts_created_at_index" ON "posts" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "posts_creator_id_index" ON "posts" USING btree ("creator_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "posts_organization_id_index" ON "posts" USING btree ("organization_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "posts_pinned_at_index" ON "posts" USING btree ("pinned_at");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "posts_poster_id_index" ON "posts" USING btree ("poster_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "posts_organization_id_index" ON "posts" USING btree ("organization_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "recurrences_created_at_index" ON "recurrences" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "recurrences_creator_id_index" ON "recurrences" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "recurrences_event_id_index" ON "recurrences" USING btree ("event_id");--> statement-breakpoint @@ -1233,24 +1131,18 @@ CREATE INDEX IF NOT EXISTS "tag_assignments_assignee_id_index" ON "tag_assignmen CREATE INDEX IF NOT EXISTS "tag_assignments_created_at_index" ON "tag_assignments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "tag_assignments_creator_id_index" ON "tag_assignments" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "tag_assignments_tag_id_index" ON "tag_assignments" USING btree ("tag_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tag_folders_created_at_index" ON "tag_folders" USING btree ("created_at");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tag_folders_creator_id_index" ON "tag_folders" USING btree ("creator_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tag_folders_name_index" ON "tag_folders" USING btree ("name");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tag_folders_organization_id_index" ON "tag_folders" USING btree ("organization_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "tag_folders_name_organization_id_index" ON "tag_folders" USING btree ("name","organization_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tags_created_at_index" ON "tags" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "tags_creator_id_index" ON "tags" USING btree ("creator_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "tags_folder_id_index" ON "tags" USING btree ("folder_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "tags_is_folder_index" ON "tags" USING btree ("is_folder");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "tags_name_index" ON "tags" USING btree ("name");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "tags_organization_id_index" ON "tags" USING btree ("organization_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "tags_name_organization_id_index" ON "tags" USING btree ("name","organization_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "tags_parent_tag_id_index" ON "tags" USING btree ("parent_tag_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "tags_is_folder_name_organization_id_index" ON "tags" USING btree ("is_folder","name","organization_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "users_creator_id_index" ON "users" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "users_name_index" ON "users" USING btree ("name");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "users_updater_id_index" ON "users" USING btree ("updater_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_attachments_created_at_index" ON "venue_attachments" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_attachments_creator_id_index" ON "venue_attachments" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_attachments_venue_id_index" ON "venue_attachments" USING btree ("venue_id");--> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "venue_attachments_position_venue_id_index" ON "venue_attachments" USING btree ("position","venue_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_bookings_created_at_index" ON "venue_bookings" USING btree ("created_at");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_bookings_creator_id_index" ON "venue_bookings" USING btree ("creator_id");--> statement-breakpoint CREATE INDEX IF NOT EXISTS "venue_bookings_event_id_index" ON "venue_bookings" USING btree ("event_id");--> statement-breakpoint diff --git a/drizzle_migrations/meta/20241118154748_snapshot.json b/drizzle_migrations/meta/20241205130831_snapshot.json similarity index 89% rename from drizzle_migrations/meta/20241118154748_snapshot.json rename to drizzle_migrations/meta/20241205130831_snapshot.json index 8c4ba16965..68be3e99a5 100644 --- a/drizzle_migrations/meta/20241118154748_snapshot.json +++ b/drizzle_migrations/meta/20241205130831_snapshot.json @@ -1,5 +1,5 @@ { - "id": "63b64a5c-c24d-4630-ab56-0f6ee417f398", + "id": "2740ad03-f2e6-4c20-a0f3-4b7ec7dc5cdf", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -485,9 +485,10 @@ "primaryKey": false, "notNull": true }, - "position": { - "name": "position", - "type": "integer", + "type": { + "name": "type", + "type": "advertisement_attachment_type", + "typeSchema": "public", "primaryKey": false, "notNull": true }, @@ -508,13 +509,6 @@ "type": "text", "primaryKey": false, "notNull": true - }, - "type": { - "name": "type", - "type": "advertisement_attachment_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true } }, "indexes": { @@ -562,27 +556,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "advertisement_attachments_advertisement_id_position_index": { - "name": "advertisement_attachments_advertisement_id_position_index", - "columns": [ - { - "expression": "advertisement_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -647,7 +620,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "description": { "name": "description", @@ -706,21 +679,6 @@ } }, "indexes": { - "advertisements_created_at_index": { - "name": "advertisements_created_at_index", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "advertisements_creator_id_index": { "name": "advertisements_creator_id_index", "columns": [ @@ -829,8 +787,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "advertisements_organization_id_organizations_id_fk": { "name": "advertisements_organization_id_organizations_id_fk", @@ -842,8 +800,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "advertisements_updater_id_users_id_fk": { "name": "advertisements_updater_id_users_id_fk", @@ -855,8 +813,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -1187,12 +1145,6 @@ "primaryKey": false, "notNull": false }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true - }, "updated_at": { "name": "updated_at", "type": "timestamp (3) with time zone", @@ -1302,27 +1254,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "agenda_sections_event_id_position_index": { - "name": "agenda_sections_event_id_position_index", - "columns": [ - { - "expression": "event_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -1406,6 +1337,12 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, "notNull": true }, "type": { @@ -1426,12 +1363,6 @@ "type": "uuid", "primaryKey": false, "notNull": false - }, - "voter_id": { - "name": "voter_id", - "type": "uuid", - "primaryKey": false, - "notNull": false } }, "indexes": { @@ -1450,21 +1381,6 @@ "method": "btree", "with": {} }, - "comment_votes_created_at_index": { - "name": "comment_votes_created_at_index", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "comment_votes_creator_id_index": { "name": "comment_votes_creator_id_index", "columns": [ @@ -1495,17 +1411,23 @@ "method": "btree", "with": {} }, - "comment_votes_voter_id_index": { - "name": "comment_votes_voter_id_index", + "comment_votes_comment_id_creator_id_index": { + "name": "comment_votes_comment_id_creator_id_index", "columns": [ { - "expression": "voter_id", + "expression": "comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "creator_id", "isExpression": false, "asc": true, "nulls": "last" } ], - "isUnique": false, + "isUnique": true, "concurrently": false, "method": "btree", "with": {} @@ -1522,8 +1444,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "comment_votes_creator_id_users_id_fk": { "name": "comment_votes_creator_id_users_id_fk", @@ -1535,8 +1457,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "comment_votes_updated_id_users_id_fk": { "name": "comment_votes_updated_id_users_id_fk", @@ -1548,32 +1470,11 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "comment_votes_voter_id_users_id_fk": { - "name": "comment_votes_voter_id_users_id_fk", - "tableFrom": "comment_votes", - "tableTo": "users", - "columnsFrom": [ - "voter_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "comment_votes_comment_id_voter_id_pk": { - "name": "comment_votes_comment_id_voter_id_pk", - "columns": [ - "comment_id", - "voter_id" - ] + "onDelete": "set null", + "onUpdate": "cascade" } }, + "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, "checkConstraints": {}, @@ -1589,12 +1490,6 @@ "primaryKey": false, "notNull": true }, - "commenter_id": { - "name": "commenter_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, "created_at": { "name": "created_at", "type": "timestamp (3) with time zone", @@ -1606,7 +1501,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "id": { "name": "id", @@ -1614,24 +1509,6 @@ "primaryKey": true, "notNull": true }, - "parent_comment_id": { - "name": "parent_comment_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "pinned_at": { - "name": "pinned_at", - "type": "timestamp (3) with time zone", - "primaryKey": false, - "notNull": false - }, - "pinner_id": { - "name": "pinner_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, "post_id": { "name": "post_id", "type": "uuid", @@ -1652,21 +1529,6 @@ } }, "indexes": { - "comments_commenter_id_index": { - "name": "comments_commenter_id_index", - "columns": [ - { - "expression": "commenter_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "comments_created_at_index": { "name": "comments_created_at_index", "columns": [ @@ -1697,36 +1559,6 @@ "method": "btree", "with": {} }, - "comments_parent_comment_id_index": { - "name": "comments_parent_comment_id_index", - "columns": [ - { - "expression": "parent_comment_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "comments_pinned_at_index": { - "name": "comments_pinned_at_index", - "columns": [ - { - "expression": "pinned_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "comments_post_id_index": { "name": "comments_post_id_index", "columns": [ @@ -1744,19 +1576,6 @@ } }, "foreignKeys": { - "comments_commenter_id_users_id_fk": { - "name": "comments_commenter_id_users_id_fk", - "tableFrom": "comments", - "tableTo": "users", - "columnsFrom": [ - "commenter_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, "comments_creator_id_users_id_fk": { "name": "comments_creator_id_users_id_fk", "tableFrom": "comments", @@ -1767,34 +1586,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "comments_parent_comment_id_comments_id_fk": { - "name": "comments_parent_comment_id_comments_id_fk", - "tableFrom": "comments", - "tableTo": "comments", - "columnsFrom": [ - "parent_comment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "comments_pinner_id_users_id_fk": { - "name": "comments_pinner_id_users_id_fk", - "tableFrom": "comments", - "tableTo": "users", - "columnsFrom": [ - "pinner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "comments_post_id_posts_id_fk": { "name": "comments_post_id_posts_id_fk", @@ -1806,8 +1599,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "comments_updater_id_users_id_fk": { "name": "comments_updater_id_users_id_fk", @@ -1819,8 +1612,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -1852,12 +1645,6 @@ "primaryKey": false, "notNull": true }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true - }, "updated_at": { "name": "updated_at", "type": "timestamp (3) with time zone", @@ -1929,27 +1716,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "event_attachments_event_id_position_index": { - "name": "event_attachments_event_id_position_index", - "columns": [ - { - "expression": "event_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -3239,13 +3005,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true - }, - "is_approved": { - "name": "is_approved", - "type": "boolean", - "primaryKey": false, - "notNull": true + "notNull": false }, "member_id": { "name": "member_id", @@ -3310,11 +3070,11 @@ "method": "btree", "with": {} }, - "organization_memberships_is_approved_index": { - "name": "organization_memberships_is_approved_index", + "organization_memberships_member_id_index": { + "name": "organization_memberships_member_id_index", "columns": [ { - "expression": "is_approved", + "expression": "member_id", "isExpression": false, "asc": true, "nulls": "last" @@ -3325,11 +3085,11 @@ "method": "btree", "with": {} }, - "organization_memberships_member_id_index": { - "name": "organization_memberships_member_id_index", + "organization_memberships_organization_id_index": { + "name": "organization_memberships_organization_id_index", "columns": [ { - "expression": "member_id", + "expression": "organization_id", "isExpression": false, "asc": true, "nulls": "last" @@ -3340,11 +3100,11 @@ "method": "btree", "with": {} }, - "organization_memberships_organization_id_index": { - "name": "organization_memberships_organization_id_index", + "organization_memberships_role_index": { + "name": "organization_memberships_role_index", "columns": [ { - "expression": "organization_id", + "expression": "role", "isExpression": false, "asc": true, "nulls": "last" @@ -3367,8 +3127,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "organization_memberships_member_id_users_id_fk": { "name": "organization_memberships_member_id_users_id_fk", @@ -3380,8 +3140,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "organization_memberships_organization_id_organizations_id_fk": { "name": "organization_memberships_organization_id_organizations_id_fk", @@ -3393,8 +3153,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "organization_memberships_updater_id_users_id_fk": { "name": "organization_memberships_updater_id_users_id_fk", @@ -3406,8 +3166,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": { @@ -3464,7 +3224,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "description": { "name": "description", @@ -3478,12 +3238,6 @@ "primaryKey": true, "notNull": true }, - "is_private": { - "name": "is_private", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, "name": { "name": "name", "type": "text", @@ -3573,8 +3327,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "organizations_updater_id_users_id_fk": { "name": "organizations_updater_id_users_id_fk", @@ -3586,8 +3340,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -3851,12 +3605,6 @@ "primaryKey": false, "notNull": true }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true - }, "post_id": { "name": "post_id", "type": "uuid", @@ -3934,27 +3682,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "post_attachments_position_post_id_index": { - "name": "post_attachments_position_post_id_index", - "columns": [ - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "post_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -4019,6 +3746,12 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, "notNull": true }, "post_id": { @@ -4027,6 +3760,13 @@ "primaryKey": false, "notNull": true }, + "type": { + "name": "type", + "type": "post_vote_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, "updated_at": { "name": "updated_at", "type": "timestamp (3) with time zone", @@ -4038,37 +3778,9 @@ "type": "uuid", "primaryKey": false, "notNull": false - }, - "type": { - "name": "type", - "type": "post_vote_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "voter_id": { - "name": "voter_id", - "type": "uuid", - "primaryKey": false, - "notNull": false } }, "indexes": { - "post_votes_created_at_index": { - "name": "post_votes_created_at_index", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "post_votes_creator_id_index": { "name": "post_votes_creator_id_index", "columns": [ @@ -4114,17 +3826,23 @@ "method": "btree", "with": {} }, - "post_votes_voter_id_index": { - "name": "post_votes_voter_id_index", + "post_votes_creator_id_post_id_index": { + "name": "post_votes_creator_id_post_id_index", "columns": [ { - "expression": "voter_id", + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "post_id", "isExpression": false, "asc": true, "nulls": "last" } ], - "isUnique": false, + "isUnique": true, "concurrently": false, "method": "btree", "with": {} @@ -4141,8 +3859,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "post_votes_post_id_posts_id_fk": { "name": "post_votes_post_id_posts_id_fk", @@ -4154,8 +3872,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "post_votes_updated_id_users_id_fk": { "name": "post_votes_updated_id_users_id_fk", @@ -4167,32 +3885,11 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "post_votes_voter_id_users_id_fk": { - "name": "post_votes_voter_id_users_id_fk", - "tableFrom": "post_votes", - "tableTo": "users", - "columnsFrom": [ - "voter_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "post_votes_post_id_voter_id_pk": { - "name": "post_votes_post_id_voter_id_pk", - "columns": [ - "post_id", - "voter_id" - ] + "onDelete": "set null", + "onUpdate": "cascade" } }, + "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, "checkConstraints": {}, @@ -4239,18 +3936,6 @@ "primaryKey": false, "notNull": false }, - "pinner_id": { - "name": "pinner_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "poster_id": { - "name": "poster_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, "updated_at": { "name": "updated_at", "type": "timestamp (3) with time zone", @@ -4295,21 +3980,6 @@ "method": "btree", "with": {} }, - "posts_organization_id_index": { - "name": "posts_organization_id_index", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "posts_pinned_at_index": { "name": "posts_pinned_at_index", "columns": [ @@ -4325,11 +3995,11 @@ "method": "btree", "with": {} }, - "posts_poster_id_index": { - "name": "posts_poster_id_index", + "posts_organization_id_index": { + "name": "posts_organization_id_index", "columns": [ { - "expression": "poster_id", + "expression": "organization_id", "isExpression": false, "asc": true, "nulls": "last" @@ -4352,8 +4022,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "posts_organization_id_organizations_id_fk": { "name": "posts_organization_id_organizations_id_fk", @@ -4365,34 +4035,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "posts_pinner_id_users_id_fk": { - "name": "posts_pinner_id_users_id_fk", - "tableFrom": "posts", - "tableTo": "users", - "columnsFrom": [ - "pinner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "posts_poster_id_users_id_fk": { - "name": "posts_poster_id_users_id_fk", - "tableFrom": "posts", - "tableTo": "users", - "columnsFrom": [ - "poster_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "posts_updater_id_users_id_fk": { "name": "posts_updater_id_users_id_fk", @@ -4404,8 +4048,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -4620,13 +4264,13 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "tag_id": { "name": "tag_id", "type": "uuid", "primaryKey": false, - "notNull": false + "notNull": true }, "updated_at": { "name": "updated_at", @@ -4714,8 +4358,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "tag_assignments_creator_id_users_id_fk": { "name": "tag_assignments_creator_id_users_id_fk", @@ -4727,8 +4371,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "tag_assignments_tag_id_tags_id_fk": { "name": "tag_assignments_tag_id_tags_id_fk", @@ -4740,8 +4384,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "tag_assignments_updater_id_users_id_fk": { "name": "tag_assignments_updater_id_users_id_fk", @@ -4753,8 +4397,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": { @@ -4771,8 +4415,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tag_folders": { - "name": "tag_folders", + "public.tags": { + "name": "tags", "schema": "", "columns": { "created_at": { @@ -4786,7 +4430,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "id": { "name": "id", @@ -4794,6 +4438,12 @@ "primaryKey": true, "notNull": true }, + "is_folder": { + "name": "is_folder", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, "name": { "name": "name", "type": "text", @@ -4806,8 +4456,8 @@ "primaryKey": false, "notNull": true }, - "parent_folder_id": { - "name": "parent_folder_id", + "parent_tag_id": { + "name": "parent_tag_id", "type": "uuid", "primaryKey": false, "notNull": false @@ -4826,23 +4476,8 @@ } }, "indexes": { - "tag_folders_created_at_index": { - "name": "tag_folders_created_at_index", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "tag_folders_creator_id_index": { - "name": "tag_folders_creator_id_index", + "tags_creator_id_index": { + "name": "tags_creator_id_index", "columns": [ { "expression": "creator_id", @@ -4856,11 +4491,11 @@ "method": "btree", "with": {} }, - "tag_folders_name_index": { - "name": "tag_folders_name_index", + "tags_is_folder_index": { + "name": "tags_is_folder_index", "columns": [ { - "expression": "name", + "expression": "is_folder", "isExpression": false, "asc": true, "nulls": "last" @@ -4871,181 +4506,14 @@ "method": "btree", "with": {} }, - "tag_folders_organization_id_index": { - "name": "tag_folders_organization_id_index", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "tag_folders_name_organization_id_index": { - "name": "tag_folders_name_organization_id_index", + "tags_name_index": { + "name": "tags_name_index", "columns": [ { "expression": "name", "isExpression": false, "asc": true, "nulls": "last" - }, - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "tag_folders_creator_id_users_id_fk": { - "name": "tag_folders_creator_id_users_id_fk", - "tableFrom": "tag_folders", - "tableTo": "users", - "columnsFrom": [ - "creator_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "tag_folders_organization_id_organizations_id_fk": { - "name": "tag_folders_organization_id_organizations_id_fk", - "tableFrom": "tag_folders", - "tableTo": "organizations", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "tag_folders_parent_folder_id_tag_folders_id_fk": { - "name": "tag_folders_parent_folder_id_tag_folders_id_fk", - "tableFrom": "tag_folders", - "tableTo": "tag_folders", - "columnsFrom": [ - "parent_folder_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "tag_folders_updater_id_users_id_fk": { - "name": "tag_folders_updater_id_users_id_fk", - "tableFrom": "tag_folders", - "tableTo": "users", - "columnsFrom": [ - "updater_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.tags": { - "name": "tags", - "schema": "", - "columns": { - "created_at": { - "name": "created_at", - "type": "timestamp (3) with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "creator_id": { - "name": "creator_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "folder_id": { - "name": "folder_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp (3) with time zone", - "primaryKey": false, - "notNull": false - }, - "updater_id": { - "name": "updater_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "tags_created_at_index": { - "name": "tags_created_at_index", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "tags_creator_id_index": { - "name": "tags_creator_id_index", - "columns": [ - { - "expression": "creator_id", - "isExpression": false, - "asc": true, - "nulls": "last" } ], "isUnique": false, @@ -5053,11 +4521,11 @@ "method": "btree", "with": {} }, - "tags_folder_id_index": { - "name": "tags_folder_id_index", + "tags_organization_id_index": { + "name": "tags_organization_id_index", "columns": [ { - "expression": "folder_id", + "expression": "organization_id", "isExpression": false, "asc": true, "nulls": "last" @@ -5068,11 +4536,11 @@ "method": "btree", "with": {} }, - "tags_name_index": { - "name": "tags_name_index", + "tags_parent_tag_id_index": { + "name": "tags_parent_tag_id_index", "columns": [ { - "expression": "name", + "expression": "parent_tag_id", "isExpression": false, "asc": true, "nulls": "last" @@ -5083,24 +4551,15 @@ "method": "btree", "with": {} }, - "tags_organization_id_index": { - "name": "tags_organization_id_index", + "tags_is_folder_name_organization_id_index": { + "name": "tags_is_folder_name_organization_id_index", "columns": [ { - "expression": "organization_id", + "expression": "is_folder", "isExpression": false, "asc": true, "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "tags_name_organization_id_index": { - "name": "tags_name_organization_id_index", - "columns": [ + }, { "expression": "name", "isExpression": false, @@ -5131,34 +4590,34 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, - "tags_folder_id_tag_folders_id_fk": { - "name": "tags_folder_id_tag_folders_id_fk", + "tags_organization_id_organizations_id_fk": { + "name": "tags_organization_id_organizations_id_fk", "tableFrom": "tags", - "tableTo": "tag_folders", + "tableTo": "organizations", "columnsFrom": [ - "folder_id" + "organization_id" ], "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, - "tags_organization_id_organizations_id_fk": { - "name": "tags_organization_id_organizations_id_fk", + "tags_parent_tag_id_tags_id_fk": { + "name": "tags_parent_tag_id_tags_id_fk", "tableFrom": "tags", - "tableTo": "organizations", + "tableTo": "tags", "columnsFrom": [ - "organization_id" + "parent_tag_id" ], "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "cascade", + "onUpdate": "cascade" }, "tags_updater_id_users_id_fk": { "name": "tags_updater_id_users_id_fk", @@ -5170,8 +4629,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -5226,7 +4685,7 @@ "name": "creator_id", "type": "uuid", "primaryKey": false, - "notNull": true + "notNull": false }, "description": { "name": "description", @@ -5400,8 +4859,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" }, "users_updater_id_users_id_fk": { "name": "users_updater_id_users_id_fk", @@ -5413,8 +4872,8 @@ "columnsTo": [ "id" ], - "onDelete": "no action", - "onUpdate": "no action" + "onDelete": "set null", + "onUpdate": "cascade" } }, "compositePrimaryKeys": {}, @@ -5448,12 +4907,6 @@ "primaryKey": false, "notNull": true }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true - }, "type": { "name": "type", "type": "venue_attachment_type", @@ -5531,27 +4984,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "venue_attachments_position_venue_id_index": { - "name": "venue_attachments_position_venue_id_index", - "columns": [ - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "venue_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -6669,7 +6101,7 @@ "schema": "public", "values": [ "administrator", - "base" + "regular" ] }, "public.post_attachment_type": { @@ -6755,7 +6187,7 @@ "schema": "public", "values": [ "administrator", - "base" + "regular" ] }, "public.venue_attachment_type": { diff --git a/drizzle_migrations/meta/_journal.json b/drizzle_migrations/meta/_journal.json index ffd5d170ad..56bfd2b9fa 100644 --- a/drizzle_migrations/meta/_journal.json +++ b/drizzle_migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1731944868624, - "tag": "20241118154748_bizarre_steve_rogers", + "when": 1733404111498, + "tag": "20241205130831_cynical_meltdown", "breakpoints": true } ] diff --git a/package.json b/package.json index 6539473791..0447bca9cb 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,23 @@ }, "dependencies": { "@fastify/cors": "^10.0.1", - "@fastify/helmet": "^12.0.1", + "@fastify/helmet": "^13.0.0", "@fastify/jwt": "^9.0.1", "@fastify/rate-limit": "^10.2.1", - "@fastify/type-provider-typebox": "^5.0.1", + "@fastify/type-provider-typebox": "^5.1.0", "@node-rs/argon2": "^2.0.0", "@pothos/core": "^4.3.0", - "@sinclair/typebox": "^0.34.3", + "@pothos/plugin-relay": "^4.3.0", + "@sinclair/typebox": "^0.34.10", "ajv-formats": "^3.0.1", "close-with-grace": "^2.1.0", - "drizzle-orm": "^0.36.3", + "drizzle-orm": "^0.37.0", "drizzle-zod": "^0.5.1", "env-schema": "^6.0.0", "fastify": "^5.1.0", "fastify-plugin": "^5.0.1", "graphql": "^16.9.0", - "graphql-scalars": "^1.23.0", + "graphql-scalars": "^1.24.0", "mercurius": "^15.1.0", "postgres": "^3.4.5", "uuidv7": "^1.0.2", @@ -29,23 +30,23 @@ "description": "talawa-api is a backend repository written using Node.js and graphql for generating APIs for the talawa flutter app.", "devDependencies": { "@biomejs/biome": "^1.9.4", - "@faker-js/faker": "^9.2.0", - "@swc/cli": "0.5.0", - "@swc/core": "^1.9.2", - "@types/node": "^22.9.0", - "@vitest/coverage-v8": "^2.1.5", - "drizzle-kit": "^0.28.1", + "@faker-js/faker": "^9.3.0", + "@swc/cli": "0.5.2", + "@swc/core": "^1.10.0", + "@types/node": "^22.10.1", + "@vitest/coverage-v8": "^2.1.8", + "drizzle-kit": "^0.29.1", "gql.tada": "^1.8.10", - "lefthook": "^1.8.4", + "lefthook": "^1.8.5", "mercurius-integration-testing": "^9.0.0", "pino-pretty": "^13.0.0", "tsx": "^4.19.2", - "typescript": "^5.6.3", - "vite-tsconfig-paths": "^5.1.2", - "vitest": "^2.1.5" + "typescript": "^5.7.2", + "vite-tsconfig-paths": "^5.1.3", + "vitest": "^2.1.8" }, "engines": { - "node": "22.11.0" + "node": "22.12.0" }, "homepage": "https://github.com/PalisadoesFoundation/talawa-api#readme", "keywords": [ @@ -57,7 +58,7 @@ "license": "GNU General Public License v3.0", "main": "./dist/index.js", "name": "talawa-api", - "packageManager": "pnpm@9.13.2", + "packageManager": "pnpm@9.14.4", "repository": { "type": "git", "url": "https://github.com/PalisadoesFoundation/talawa-api" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00ebba1ff6..adc1b1d08d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^10.0.1 version: 10.0.1 '@fastify/helmet': - specifier: ^12.0.1 - version: 12.0.1 + specifier: ^13.0.0 + version: 13.0.0 '@fastify/jwt': specifier: ^9.0.1 version: 9.0.1 @@ -21,17 +21,20 @@ importers: specifier: ^10.2.1 version: 10.2.1 '@fastify/type-provider-typebox': - specifier: ^5.0.1 - version: 5.0.1(@sinclair/typebox@0.34.3) + specifier: ^5.1.0 + version: 5.1.0(@sinclair/typebox@0.34.10) '@node-rs/argon2': specifier: ^2.0.0 version: 2.0.0 '@pothos/core': specifier: ^4.3.0 version: 4.3.0(graphql@16.9.0) + '@pothos/plugin-relay': + specifier: ^4.3.0 + version: 4.3.0(@pothos/core@4.3.0(graphql@16.9.0))(graphql@16.9.0) '@sinclair/typebox': - specifier: ^0.34.3 - version: 0.34.3 + specifier: ^0.34.10 + version: 0.34.10 ajv-formats: specifier: ^3.0.1 version: 3.0.1(ajv@8.17.1) @@ -39,11 +42,11 @@ importers: specifier: ^2.1.0 version: 2.1.0 drizzle-orm: - specifier: ^0.36.3 - version: 0.36.3(@libsql/client-wasm@0.14.0)(postgres@3.4.5) + specifier: ^0.37.0 + version: 0.37.0(@libsql/client-wasm@0.14.0)(postgres@3.4.5) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.36.3(@libsql/client-wasm@0.14.0)(postgres@3.4.5))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.37.0(@libsql/client-wasm@0.14.0)(postgres@3.4.5))(zod@3.23.8) env-schema: specifier: ^6.0.0 version: 6.0.0 @@ -57,8 +60,8 @@ importers: specifier: ^16.9.0 version: 16.9.0 graphql-scalars: - specifier: ^1.23.0 - version: 1.23.0(graphql@16.9.0) + specifier: ^1.24.0 + version: 1.24.0(graphql@16.9.0) mercurius: specifier: ^15.1.0 version: 15.1.0(graphql@16.9.0) @@ -76,29 +79,29 @@ importers: specifier: ^1.9.4 version: 1.9.4 '@faker-js/faker': - specifier: ^9.2.0 - version: 9.2.0 + specifier: ^9.3.0 + version: 9.3.0 '@swc/cli': - specifier: 0.5.0 - version: 0.5.0(@swc/core@1.9.2) + specifier: 0.5.2 + version: 0.5.2(@swc/core@1.10.0) '@swc/core': - specifier: ^1.9.2 - version: 1.9.2 + specifier: ^1.10.0 + version: 1.10.0 '@types/node': - specifier: ^22.9.0 - version: 22.9.0 + specifier: ^22.10.1 + version: 22.10.1 '@vitest/coverage-v8': - specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.9.0)) + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@22.10.1)) drizzle-kit: - specifier: ^0.28.1 - version: 0.28.1 + specifier: ^0.29.1 + version: 0.29.1 gql.tada: specifier: ^1.8.10 - version: 1.8.10(graphql@16.9.0)(typescript@5.6.3) + version: 1.8.10(graphql@16.9.0)(typescript@5.7.2) lefthook: - specifier: ^1.8.4 - version: 1.8.4 + specifier: ^1.8.5 + version: 1.8.5 mercurius-integration-testing: specifier: ^9.0.0 version: 9.0.0(fastify@5.1.0)(graphql@16.9.0)(mercurius@15.1.0(graphql@16.9.0)) @@ -109,14 +112,14 @@ importers: specifier: ^4.19.2 version: 4.19.2 typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.7.2 + version: 5.7.2 vite-tsconfig-paths: - specifier: ^5.1.2 - version: 5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)) + specifier: ^5.1.3 + version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)) vitest: - specifier: ^2.1.5 - version: 2.1.5(@types/node@22.9.0) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1) packages: @@ -146,13 +149,13 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -783,8 +786,8 @@ packages: cpu: [x64] os: [win32] - '@faker-js/faker@9.2.0': - resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} + '@faker-js/faker@9.3.0': + resolution: {integrity: sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} '@fastify/accept-negotiator@2.0.0': @@ -802,8 +805,8 @@ packages: '@fastify/fast-json-stringify-compiler@5.0.1': resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==} - '@fastify/helmet@12.0.1': - resolution: {integrity: sha512-kkjBcedWwdflRThovGuvN9jB2QQLytBqArCFPdMIb7o2Fp0l/H3xxYi/6x/SSRuH/FFt9qpTGIfJz2bfnMrLqA==} + '@fastify/helmet@13.0.0': + resolution: {integrity: sha512-7Ksj/9UDelZ9VhUUzVkBMRRzSSiX71T/ICT5+3m2GHmrWgnARu8OlE7ZaR7DjZhZjWzUWACst4oLjNdPSkVslw==} '@fastify/jwt@9.0.1': resolution: {integrity: sha512-+vnlUi7Rwi5lihuPxCIqOzla7C+wk7rIzLf09xlxpwqRKXpun7kgIM6LLc+J1Iv0IidlxdOQmCiXmB52Q74MVA==} @@ -814,16 +817,16 @@ packages: '@fastify/rate-limit@10.2.1': resolution: {integrity: sha512-6rM4MXBtz9j6i9ChAVNz8ZA2yzSTGswIR3XvIbzHpe7JUWvbL7NJylFO12n7zKWfl3rEpOYiKogD98scl8SMGQ==} - '@fastify/send@3.1.1': - resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==} + '@fastify/send@3.3.0': + resolution: {integrity: sha512-hvrgPVG3oehn4wSPmRdqZcBCsEt7Lp6WOd6vsJ3Ms4hc5r5zouT9Ls9wq6R2tHMgJGHhNtsmd0CnhP7lmF7OTg==} - '@fastify/static@8.0.2': - resolution: {integrity: sha512-xJ+XaZVl4Y+lKztx8jGi+BE73aByhOmjMgaTx98E4XtVZxUpiaYQIMBlwACsJz+xohm0kvzV34BZoiZ+bsJtBQ==} + '@fastify/static@8.0.3': + resolution: {integrity: sha512-GHSoOVDIxEYEeVR5l044bRCuAKDErD/+9VE+Z9fnaTRr+DDz0Avrm4kKai1mHbPx6C0U7BVNthjd/gcMquZZUA==} - '@fastify/type-provider-typebox@5.0.1': - resolution: {integrity: sha512-zepdCWmgvpcLS06DN5vznMJLUP/5gLt/X3lVXZvddXmHSImQxm2Em+dV64b6x9P4G4V9DKuS4EL08ASjpCboNA==} + '@fastify/type-provider-typebox@5.1.0': + resolution: {integrity: sha512-F1AQHeLiKp1hu6GMWm5W6fZ6zXQ0mTV+qHOzrptAie9AYewvFr5Q3blfy8Qmx9gUgwA3Yj+CWvQQJeTwDgTnIg==} peerDependencies: - '@sinclair/typebox': '>=0.26 <=0.33' + '@sinclair/typebox': '>=0.26 <=0.34' '@fastify/websocket@11.0.1': resolution: {integrity: sha512-44yam5+t1I9v09hWBYO+ezV88+mb9Se2BjgERtzB/68+0mGeTfFkjBeDBe2y+ZdiPpeO2rhevhdnfrBm5mqH+Q==} @@ -891,10 +894,6 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} - '@mole-inc/bin-wrapper@8.0.1': - resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - '@napi-rs/nice-android-arm-eabi@1.0.1': resolution: {integrity: sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==} engines: {node: '>= 10'} @@ -1106,105 +1105,114 @@ packages: peerDependencies: graphql: '>=16.6.0' - '@rollup/rollup-android-arm-eabi@4.27.2': - resolution: {integrity: sha512-Tj+j7Pyzd15wAdSJswvs5CJzJNV+qqSUcr/aCD+jpQSBtXvGnV0pnrjoc8zFTe9fcKCatkpFpOO7yAzpO998HA==} + '@pothos/plugin-relay@4.3.0': + resolution: {integrity: sha512-sa8PWNQJLSNf7AVY43T3rSq4EnspuAcrPWcH3mCyocaqKzCqZA8DOdJ0tTWeGCJp1lsWkKFSrdb81g9FVJGTIg==} + peerDependencies: + '@pothos/core': '*' + graphql: '>=16.6.0' + + '@rollup/rollup-android-arm-eabi@4.28.0': + resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.27.2': - resolution: {integrity: sha512-xsPeJgh2ThBpUqlLgRfiVYBEf/P1nWlWvReG+aBWfNv3XEBpa6ZCmxSVnxJgLgkNz4IbxpLy64h2gCmAAQLneQ==} + '@rollup/rollup-android-arm64@4.28.0': + resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.27.2': - resolution: {integrity: sha512-KnXU4m9MywuZFedL35Z3PuwiTSn/yqRIhrEA9j+7OSkji39NzVkgxuxTYg5F8ryGysq4iFADaU5osSizMXhU2A==} + '@rollup/rollup-darwin-arm64@4.28.0': + resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.27.2': - resolution: {integrity: sha512-Hj77A3yTvUeCIx/Vi+4d4IbYhyTwtHj07lVzUgpUq9YpJSEiGJj4vXMKwzJ3w5zp5v3PFvpJNgc/J31smZey6g==} + '@rollup/rollup-darwin-x64@4.28.0': + resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.27.2': - resolution: {integrity: sha512-RjgKf5C3xbn8gxvCm5VgKZ4nn0pRAIe90J0/fdHUsgztd3+Zesb2lm2+r6uX4prV2eUByuxJNdt647/1KPRq5g==} + '@rollup/rollup-freebsd-arm64@4.28.0': + resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.27.2': - resolution: {integrity: sha512-duq21FoXwQtuws+V9H6UZ+eCBc7fxSpMK1GQINKn3fAyd9DFYKPJNcUhdIKOrMFjLEJgQskoMoiuizMt+dl20g==} + '@rollup/rollup-freebsd-x64@4.28.0': + resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.27.2': - resolution: {integrity: sha512-6npqOKEPRZkLrMcvyC/32OzJ2srdPzCylJjiTJT2c0bwwSGm7nz2F9mNQ1WrAqCBZROcQn91Fno+khFhVijmFA==} + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': + resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.27.2': - resolution: {integrity: sha512-V9Xg6eXtgBtHq2jnuQwM/jr2mwe2EycnopO8cbOvpzFuySCGtKlPCI3Hj9xup/pJK5Q0388qfZZy2DqV2J8ftw==} + '@rollup/rollup-linux-arm-musleabihf@4.28.0': + resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.27.2': - resolution: {integrity: sha512-uCFX9gtZJoQl2xDTpRdseYuNqyKkuMDtH6zSrBTA28yTfKyjN9hQ2B04N5ynR8ILCoSDOrG/Eg+J2TtJ1e/CSA==} + '@rollup/rollup-linux-arm64-gnu@4.28.0': + resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.27.2': - resolution: {integrity: sha512-/PU9P+7Rkz8JFYDHIi+xzHabOu9qEWR07L5nWLIUsvserrxegZExKCi2jhMZRd0ATdboKylu/K5yAXbp7fYFvA==} + '@rollup/rollup-linux-arm64-musl@4.28.0': + resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.27.2': - resolution: {integrity: sha512-eCHmol/dT5odMYi/N0R0HC8V8QE40rEpkyje/ZAXJYNNoSfrObOvG/Mn+s1F/FJyB7co7UQZZf6FuWnN6a7f4g==} + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': + resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.27.2': - resolution: {integrity: sha512-DEP3Njr9/ADDln3kNi76PXonLMSSMiCir0VHXxmGSHxCxDfQ70oWjHcJGfiBugzaqmYdTC7Y+8Int6qbnxPBIQ==} + '@rollup/rollup-linux-riscv64-gnu@4.28.0': + resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.27.2': - resolution: {integrity: sha512-NHGo5i6IE/PtEPh5m0yw5OmPMpesFnzMIS/lzvN5vknnC1sXM5Z/id5VgcNPgpD+wHmIcuYYgW+Q53v+9s96lQ==} + '@rollup/rollup-linux-s390x-gnu@4.28.0': + resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.27.2': - resolution: {integrity: sha512-PaW2DY5Tan+IFvNJGHDmUrORadbe/Ceh8tQxi8cmdQVCCYsLoQo2cuaSj+AU+YRX8M4ivS2vJ9UGaxfuNN7gmg==} + '@rollup/rollup-linux-x64-gnu@4.28.0': + resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.27.2': - resolution: {integrity: sha512-dOlWEMg2gI91Qx5I/HYqOD6iqlJspxLcS4Zlg3vjk1srE67z5T2Uz91yg/qA8sY0XcwQrFzWWiZhMNERylLrpQ==} + '@rollup/rollup-linux-x64-musl@4.28.0': + resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.27.2': - resolution: {integrity: sha512-euMIv/4x5Y2/ImlbGl88mwKNXDsvzbWUlT7DFky76z2keajCtcbAsN9LUdmk31hAoVmJJYSThgdA0EsPeTr1+w==} + '@rollup/rollup-win32-arm64-msvc@4.28.0': + resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.27.2': - resolution: {integrity: sha512-RsnE6LQkUHlkC10RKngtHNLxb7scFykEbEwOFDjr3CeCMG+Rr+cKqlkKc2/wJ1u4u990urRHCbjz31x84PBrSQ==} + '@rollup/rollup-win32-ia32-msvc@4.28.0': + resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.27.2': - resolution: {integrity: sha512-foJM5vv+z2KQmn7emYdDLyTbkoO5bkHZE1oth2tWbQNGW7mX32d46Hz6T0MqXdWS2vBZhaEtHqdy9WYwGfiliA==} + '@rollup/rollup-win32-x64-msvc@4.28.0': + resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==} cpu: [x64] os: [win32] - '@sinclair/typebox@0.34.3': - resolution: {integrity: sha512-fEgncmnqn6WGibPn34deH5PwmMTuNCZ2clwlwevgFn8rP0l38zzWRg3KVYhoOZwkZ2Ew3yhZ/STdGDuMig66oQ==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} + '@sinclair/typebox@0.34.10': + resolution: {integrity: sha512-bJ3mIrYjEwenwwt+xAUq3GnOf1O4r2sApPzmfmF90XYMiKxjDzFSWSpWxqzSlQq3pCXuHP2UPxVPKeUFGJxb+A==} + + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} - '@swc/cli@0.5.0': - resolution: {integrity: sha512-eFsrNt85SbHTeX6svpBNcA5DQLP/wrSyCs3KVZjbuEHWD7JGpajZOIwH74lVhyrmrXOcGxgbnxXEbDIfRlLcSw==} + '@swc/cli@0.5.2': + resolution: {integrity: sha512-ul2qIqjM5bfe9zWLqFDmHZCf9HXXSZZAlZLe4czn+lH4PewO+OWZnQcYCscnJKlbx6MuWjzXVR7gkspjNEJwJA==} engines: {node: '>= 16.14.0'} hasBin: true peerDependencies: @@ -1214,68 +1222,68 @@ packages: chokidar: optional: true - '@swc/core-darwin-arm64@1.9.2': - resolution: {integrity: sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==} + '@swc/core-darwin-arm64@1.10.0': + resolution: {integrity: sha512-wCeUpanqZyzvgqWRtXIyhcFK3CqukAlYyP+fJpY2gWc/+ekdrenNIfZMwY7tyTFDkXDYEKzvn3BN/zDYNJFowQ==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.9.2': - resolution: {integrity: sha512-9gD+bwBz8ZByjP6nZTXe/hzd0tySIAjpDHgkFiUrc+5zGF+rdTwhcNrzxNHJmy6mw+PW38jqII4uspFHUqqxuQ==} + '@swc/core-darwin-x64@1.10.0': + resolution: {integrity: sha512-0CZPzqTynUBO+SHEl/qKsFSahp2Jv/P2ZRjFG0gwZY5qIcr1+B/v+o74/GyNMBGz9rft+F2WpU31gz2sJwyF4A==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.9.2': - resolution: {integrity: sha512-kYq8ief1Qrn+WmsTWAYo4r+Coul4dXN6cLFjiPZ29Cv5pyU+GFvSPAB4bEdMzwy99rCR0u2P10UExaeCjurjvg==} + '@swc/core-linux-arm-gnueabihf@1.10.0': + resolution: {integrity: sha512-oq+DdMu5uJOFPtRkeiITc4kxmd+QSmK+v+OBzlhdGkSgoH3yRWZP+H2ao0cBXo93ZgCr2LfjiER0CqSKhjGuNA==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.9.2': - resolution: {integrity: sha512-n0W4XiXlmEIVqxt+rD3ZpkogsEWUk1jJ+i5bQNgB+1JuWh0fBE8c/blDgTQXa0GB5lTPVDZQussgdNOCnAZwiA==} + '@swc/core-linux-arm64-gnu@1.10.0': + resolution: {integrity: sha512-Y6+PC8knchEViRxiCUj3j8wsGXaIhuvU+WqrFqV834eiItEMEI9+Vh3FovqJMBE3L7d4E4ZQtgImHCXjrHfxbw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.9.2': - resolution: {integrity: sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==} + '@swc/core-linux-arm64-musl@1.10.0': + resolution: {integrity: sha512-EbrX9A5U4cECCQQfky7945AW9GYnTXtCUXElWTkTYmmyQK87yCyFfY8hmZ9qMFIwxPOH6I3I2JwMhzdi8Qoz7g==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.9.2': - resolution: {integrity: sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==} + '@swc/core-linux-x64-gnu@1.10.0': + resolution: {integrity: sha512-TaxpO6snTjjfLXFYh5EjZ78se69j2gDcqEM8yB9gguPYwkCHi2Ylfmh7iVaNADnDJFtjoAQp0L41bTV/Pfq9Cg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.9.2': - resolution: {integrity: sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==} + '@swc/core-linux-x64-musl@1.10.0': + resolution: {integrity: sha512-IEGvDd6aEEKEyZFZ8oCKuik05G5BS7qwG5hO5PEMzdGeh8JyFZXxsfFXbfeAqjue4UaUUrhnoX+Ze3M2jBVMHw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.9.2': - resolution: {integrity: sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==} + '@swc/core-win32-arm64-msvc@1.10.0': + resolution: {integrity: sha512-UkQ952GSpY+Z6XONj9GSW8xGSkF53jrCsuLj0nrcuw7Dvr1a816U/9WYZmmcYS8tnG2vHylhpm6csQkyS8lpCw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.9.2': - resolution: {integrity: sha512-nLWBi4vZDdM/LkiQmPCakof8Dh1/t5EM7eudue04V1lIcqx9YHVRS3KMwEaCoHLGg0c312Wm4YgrWQd9vwZ5zQ==} + '@swc/core-win32-ia32-msvc@1.10.0': + resolution: {integrity: sha512-a2QpIZmTiT885u/mUInpeN2W9ClCnqrV2LnMqJR1/Fgx1Afw/hAtiDZPtQ0SqS8yDJ2VR5gfNZo3gpxWMrqdVA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.9.2': - resolution: {integrity: sha512-ik/k+JjRJBFkXARukdU82tSVx0CbExFQoQ78qTO682esbYXzjdB5eLVkoUbwen299pnfr88Kn4kyIqFPTje8Xw==} + '@swc/core-win32-x64-msvc@1.10.0': + resolution: {integrity: sha512-tZcCmMwf483nwsEBfUk5w9e046kMa1iSik4bP9Kwi2FGtOfHuDfIcwW4jek3hdcgF5SaBW1ktnK/lgQLDi5AtA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.9.2': - resolution: {integrity: sha512-dYyEkO6mRYtZFpnOsnYzv9rY69fHAHoawYOjGOEcxk9WYtaJhowMdP/w6NcOKnz2G7GlZaenjkzkMa6ZeQeMsg==} + '@swc/core@1.10.0': + resolution: {integrity: sha512-+CuuTCmQFfzaNGg1JmcZvdUVITQXJk9sMnl1C2TiDLzOSVOJRwVD4dNo5dljX/qxpMAN+2BIYlwjlSkoGi6grg==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -1286,12 +1294,12 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/types@0.1.15': - resolution: {integrity: sha512-XKaZ+dzDIQ9Ot9o89oJQ/aluI17+VvUnIpYJTcZtvv1iYX6MzHh3Ik2CSR7MdPKpPwfZXHBeCingb2b4PoDVdw==} + '@swc/types@0.1.17': + resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -1299,38 +1307,29 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - '@types/cacheable-request@6.0.3': - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/node@22.10.1': + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} - '@types/node@22.9.0': - resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} - - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - - '@vitest/coverage-v8@2.1.5': - resolution: {integrity: sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==} + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} peerDependencies: - '@vitest/browser': 2.1.5 - vitest: 2.1.5 + '@vitest/browser': 2.1.8 + vitest: 2.1.8 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@2.1.5': - resolution: {integrity: sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==} + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/mocker@2.1.5': - resolution: {integrity: sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 @@ -1340,20 +1339,60 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.5': - resolution: {integrity: sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==} + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + + '@xhmikosr/archive-type@7.0.0': + resolution: {integrity: sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA==} + engines: {node: ^14.14.0 || >=16.0.0} + + '@xhmikosr/bin-check@7.0.3': + resolution: {integrity: sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA==} + engines: {node: '>=18'} + + '@xhmikosr/bin-wrapper@13.0.5': + resolution: {integrity: sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tar@8.0.1': + resolution: {integrity: sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tarbz2@8.0.1': + resolution: {integrity: sha512-OF+6DysDZP5YTDO8uHuGG6fMGZjc+HszFPBkVltjoje2Cf60hjBg/YP5OQndW1hfwVWOdP7f3CnJiPZHJUTtEg==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-targz@8.0.1': + resolution: {integrity: sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg==} + engines: {node: '>=18'} - '@vitest/runner@2.1.5': - resolution: {integrity: sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==} + '@xhmikosr/decompress-unzip@7.0.0': + resolution: {integrity: sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ==} + engines: {node: '>=18'} - '@vitest/snapshot@2.1.5': - resolution: {integrity: sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==} + '@xhmikosr/decompress@10.0.1': + resolution: {integrity: sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag==} + engines: {node: '>=18'} - '@vitest/spy@2.1.5': - resolution: {integrity: sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==} + '@xhmikosr/downloader@15.0.1': + resolution: {integrity: sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g==} + engines: {node: '>=18'} - '@vitest/utils@2.1.5': - resolution: {integrity: sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==} + '@xhmikosr/os-filter-obj@3.0.0': + resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==} + engines: {node: ^14.14.0 || >=16.0.0} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -1393,8 +1432,8 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + arch@3.0.0: + resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==} asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} @@ -1410,16 +1449,18 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.5.0: + resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - bin-check@4.1.0: - resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==} - engines: {node: '>=4'} - bin-version-check@5.1.0: resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} engines: {node: '>=12'} @@ -1438,9 +1479,15 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -1448,13 +1495,13 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} - cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} chai@5.1.2: resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} @@ -1468,9 +1515,6 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - close-with-grace@2.1.0: resolution: {integrity: sha512-rME1AtzKc9dfpLr8XBVhXqhVZDvtaIA7FIpjPaO+DmDsomaTNtuEBZMoNDgIvjHYK5q8/Afxy34YTXInUBsT1A==} @@ -1484,6 +1528,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -1496,13 +1544,10 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - cookie@1.0.1: - resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1527,6 +1572,10 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + defaults@3.0.0: + resolution: {integrity: sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==} + engines: {node: '>=18'} + defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -1539,23 +1588,23 @@ packages: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} - drizzle-kit@0.28.1: - resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} + drizzle-kit@0.29.1: + resolution: {integrity: sha512-OvHL8RVyYiPR3LLRE3SHdcON8xGXl+qMfR9uTTnFWBPIqVk/3NWYZPb7nfpM1Bhix3H+BsxqPyyagG7YZ+Z63A==} hasBin: true - drizzle-orm@0.36.3: - resolution: {integrity: sha512-ffQB7CcyCTvQBK6xtRLMl/Jsd5xFTBs+UTHrgs1hbk68i5TPkbsoCPbKEwiEsQZfq2I7VH632XJpV1g7LS2H9Q==} + drizzle-orm@0.37.0: + resolution: {integrity: sha512-AsCNACQ/T2CyZUkrBRUqFT2ibHJ9ZHz3+lzYJFFn3hnj7ylIeItMz5kacRG89uSE74nXYShqehr6u+6ks4JR1A==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' + '@cloudflare/workers-types': '>=4' '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' - '@neondatabase/serverless': '>=0.1' + '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1' @@ -1697,10 +1746,6 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1712,18 +1757,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - execa@0.7.0: - resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} - engines: {node: '>=4'} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - executable@4.1.1: - resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} - engines: {node: '>=4'} - expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -1745,6 +1782,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1794,17 +1834,17 @@ packages: fastseries@1.7.2: resolution: {integrity: sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==} - file-type@17.1.6: - resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + file-type@19.6.0: + resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} + engines: {node: '>=18'} filename-reserved-regex@3.0.0: resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - filenamify@5.1.1: - resolution: {integrity: sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==} - engines: {node: '>=12.20'} + filenamify@6.0.0: + resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} + engines: {node: '>=16'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -1822,6 +1862,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1834,18 +1878,14 @@ packages: generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - get-stream@3.0.0: - resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} - engines: {node: '>=4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -1865,9 +1905,9 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} + got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} gql.tada@1.8.10: resolution: {integrity: sha512-FrvSxgz838FYVPgZHGOSgbpOjhR+yq44rCzww3oOPJYi0OvBJjAgCiP6LEokZIYND2fUTXzQAyLgcvgw1yNP5A==} @@ -1875,13 +1915,16 @@ packages: peerDependencies: typescript: ^5.0.0 + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql-jit@0.8.6: resolution: {integrity: sha512-oVJteh/uYDpIA/M4UHrI+DmzPnX1zTD0a7Je++JA8q8P68L/KbuepimDyrT5FhL4HAq3filUxaFvfsL6/A4msw==} peerDependencies: graphql: '>=15' - graphql-scalars@1.23.0: - resolution: {integrity: sha512-YTRNcwitkn8CqYcleKOx9IvedA8JIERn8BRq21nlKgOr4NEcTaWEG0sT+H92eF3ALTFbPgsqfft4cw+MGgv0Gg==} + graphql-scalars@1.24.0: + resolution: {integrity: sha512-olbFN39m0XsHHESACUdd7jWU/lGxMMS1B7NZ8XqpqhKZrjBxzeGYAnQ4Ax//huYds771wb7gCznA+65QDuUa+g==} engines: {node: '>=10'} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1894,9 +1937,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - helmet@7.2.0: - resolution: {integrity: sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==} - engines: {node: '>=16.0.0'} + helmet@8.0.0: + resolution: {integrity: sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==} + engines: {node: '>=18.0.0'} help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} @@ -1911,8 +1954,8 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} human-signals@2.1.0: @@ -1929,6 +1972,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inspect-with-kind@1.0.5: + resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -1956,14 +2002,14 @@ packages: is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2009,58 +2055,62 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - lefthook-darwin-arm64@1.8.4: - resolution: {integrity: sha512-OS5MsU0gvd8LYSpuQCHtmDUqwNrJ/LjCO0LGC1wNepY4OkuVl9DfX+rQ506CVUQYZiGVcwy2/qPOOBjNzA5+wQ==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + lefthook-darwin-arm64@1.8.5: + resolution: {integrity: sha512-BXUcE+2TTkYRGhMB+6HwAbxhK72L4kFKbukb74VfSk9HYBFb/7IQXyUgsB2dik3LSTcDcl9SgOayzeLU9EqmGw==} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@1.8.4: - resolution: {integrity: sha512-QLRsqK9aTMRcVW8qz4pzI2OWnGCEcaEPJlIiFjwstYsS+wfkooxOS0UkfVMjy+QoGgEcki+cxF/FoY7lE7DDtw==} + lefthook-darwin-x64@1.8.5: + resolution: {integrity: sha512-xkjYwVlJBQiJALR9jJi6MpIF161z6Td3gyVJoGII95h9hETYZV2J7ox4PUGufE5EV8P0AXWveWo8cJWhWVbssw==} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@1.8.4: - resolution: {integrity: sha512-chnQ1m/Cmn9c0sLdk5HL2SToE5LBJv5uQMdH1IGRRcw+nEqWqrMnDXvM75caiJAyjmUGvPH3czKTJDzTFV1E+A==} + lefthook-freebsd-arm64@1.8.5: + resolution: {integrity: sha512-kL8+HxjAtMco824pZkBJiA3+q4djldNNw06atzgAxyy8/Kj+2NjlIaez88NPQHE4hAVMqtURzmRtnaruaRtT1g==} cpu: [arm64] os: [freebsd] - lefthook-freebsd-x64@1.8.4: - resolution: {integrity: sha512-KQi+WBUdnGLnK0rHOR58kbMH5TDVN1ZjZLu66Pv9FCG7Y7shR1qtaTXu+wmxdRhMvaLeQIXRsUEPjNRC66yMmA==} + lefthook-freebsd-x64@1.8.5: + resolution: {integrity: sha512-fQfSUbAQVhLalZFl6sFGJQVBJK9EsQpmXrv1Qunhi8QOY/f07a2onsWb+jI9sp31ITTe2Jtzu0h31ZCBqTWxQw==} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@1.8.4: - resolution: {integrity: sha512-CXNcqIskLwTwQARidGdFqmNxpvOU3jsWPK4KA7pq2+QmlWJ64w98ebMvNBoUmRUCXqzmUm7Udf/jpfz2fobewQ==} + lefthook-linux-arm64@1.8.5: + resolution: {integrity: sha512-gnBonAc3Heq+Sd+MzSbtLmZkYzSDZ+tTpLAfq2Q62qnVEZpWXFd3qfS2up5a7oUEDNcos7OHDqebSbowpNilZA==} cpu: [arm64] os: [linux] - lefthook-linux-x64@1.8.4: - resolution: {integrity: sha512-pVNITkFBxUCEtamWSM/res2Gd48+m9YKbNyIBndAuZVC5pKV5aGKZy2DNq6PWUPYiUDPx+7hoAtCJg/tlAiqhw==} + lefthook-linux-x64@1.8.5: + resolution: {integrity: sha512-lLsbnXy58Syw6VMJS554HbfrBKUZ0mBWvDbr0DGoDBvFyxtvv5kFMbF/ZlRpLKl7Ds9IqCCCW0NpsjsixX4Z+A==} cpu: [x64] os: [linux] - lefthook-openbsd-arm64@1.8.4: - resolution: {integrity: sha512-l+i/Dg5X36kYzhpMGSPE3rMbWy1KSytbLB9lY1PmxYb6LRH6iQTYIoxvLabVUwSBPSq8HtIFa50+bvC5+scfVA==} + lefthook-openbsd-arm64@1.8.5: + resolution: {integrity: sha512-g3XFw/0q0T0Zd1vpPCO5l+ryFIyf+v/FFVPVb6HKvpDI4BYLFVzsZeVGzRiWZt1oJ0DI9qrrvdG4uJJBs4tSDQ==} cpu: [arm64] os: [openbsd] - lefthook-openbsd-x64@1.8.4: - resolution: {integrity: sha512-CqhDDPPX8oHzMLgNi/Reba823DRzj+eMNWQ8axvSiIG+zmG1w20xZH5QSs/mD3tjrND90yfDd90mWMt181qPyA==} + lefthook-openbsd-x64@1.8.5: + resolution: {integrity: sha512-GmfnPUMMxa/ckHS2L7zIobWusaUmDWZU0zQj016QXGYS4SN8l5ZRU/ht4Tg6bbQSO6HYhWcyfIXB1XjPJPpG7w==} cpu: [x64] os: [openbsd] - lefthook-windows-arm64@1.8.4: - resolution: {integrity: sha512-dvpvorICmVjmw29Aiczg7DcaSzkd86bEBomiGq4UsAEk3+7ExLrlWJDLFsI6xLjMKmTxy+F7eXb2uDtuFC1N4g==} + lefthook-windows-arm64@1.8.5: + resolution: {integrity: sha512-e+6QAYD7PCsfVtPtnOMSC8PUiwnLCE9xCZq+dIK65/W/Ow84lSzHBR67Wl5MI3RiiGF2g8kulG+U1YZobXbdxQ==} cpu: [arm64] os: [win32] - lefthook-windows-x64@1.8.4: - resolution: {integrity: sha512-e+y8Jt4/7PnoplhOuK48twjGVJEsU4T3J5kxD4mWfl6Cbit0YSn4bme9nW41eqCqTUqOm+ky29XlfnPHFX5ZNA==} + lefthook-windows-x64@1.8.5: + resolution: {integrity: sha512-NPZwHbSWuGmYe5EEjkQbvRaotPW9/maKK7kexqrHxjzL/mOaoHSEYlW6ByPz2RB+bvgoTCkNRXlPiDDoDSYv+w==} cpu: [x64] os: [win32] - lefthook@1.8.4: - resolution: {integrity: sha512-XNyMaTWNRuADOaocYiHidgNkNDz8SCekpdNJ7lqceFcBT2zjumnb28/o7IMaNROpLBZdQkLkJXSeaQWGqn3kog==} + lefthook@1.8.5: + resolution: {integrity: sha512-agsAaXJWoM9vOSVmKVYGPYT5IEzz4Upz0yWRva4L0mT+eObcesIrcMCnLLM+iGfweqC5exfDMm7BHUjTwoiGPg==} hasBin: true light-my-request@6.3.0: @@ -2078,9 +2128,9 @@ packages: loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2089,11 +2139,8 @@ packages: resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} engines: {node: 20 || >=22} - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - - magic-string@0.30.12: - resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -2147,14 +2194,14 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -2183,18 +2230,14 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} + normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + engines: {node: '>=14.16'} npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} @@ -2214,17 +2257,9 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - os-filter-obj@2.0.0: - resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==} - engines: {node: '>=4'} - - p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} @@ -2233,10 +2268,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2260,6 +2291,9 @@ packages: resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} engines: {node: '>=14.16'} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2267,10 +2301,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} @@ -2285,8 +2315,8 @@ packages: resolution: {integrity: sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==} hasBin: true - piscina@4.7.0: - resolution: {integrity: sha512-b8hvkpp9zS0zsfa939b/jXbe64Z2gZv0Ha7FYPNUiDIB1y2AtxcOZdfP8xN8HFjUaqQiT9gRlfjAsoL8vdJ1Iw==} + piscina@4.8.0: + resolution: {integrity: sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==} postcss@8.4.49: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} @@ -2307,9 +2337,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -2320,6 +2347,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -2339,10 +2369,6 @@ packages: resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readable-web-to-node-stream@3.0.2: - resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} - engines: {node: '>=8'} - real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -2357,8 +2383,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} ret@0.5.0: resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} @@ -2371,8 +2398,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.27.2: - resolution: {integrity: sha512-KreA+PzWmk2yaFmZVwe6GB2uBD86nXl86OsDkt1bJS9p3vqWuEQ6HnJJ+j/mZi/q0920P99/MVRlB4L3crpF5w==} + rollup@4.28.0: + resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2395,6 +2422,10 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + seek-bzip@2.0.0: + resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==} + hasBin: true + semver-regex@4.0.5: resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} engines: {node: '>=12'} @@ -2414,18 +2445,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -2493,6 +2516,9 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + streamx@2.21.0: + resolution: {integrity: sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2512,9 +2538,8 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} + strip-dirs@3.0.0: + resolution: {integrity: sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==} strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} @@ -2524,25 +2549,30 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-outer@2.0.0: - resolution: {integrity: sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - strtok3@7.1.1: - resolution: {integrity: sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==} + strtok3@9.1.1: + resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} engines: {node: '>=16'} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} + text-decoder@1.2.1: + resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} + thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-lru@11.2.11: resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==} engines: {node: '>=12'} @@ -2577,14 +2607,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - token-types@5.0.1: - resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} engines: {node: '>=14.16'} - trim-repeated@2.0.0: - resolution: {integrity: sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==} - engines: {node: '>=12'} - tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} @@ -2603,13 +2629,20 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} undici@6.21.0: resolution: {integrity: sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==} @@ -2622,13 +2655,13 @@ packages: resolution: {integrity: sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==} hasBin: true - vite-node@2.1.5: - resolution: {integrity: sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-tsconfig-paths@5.1.2: - resolution: {integrity: sha512-gEIbKfJzSEv0yR3XS2QEocKetONoWkbROj6hGx0FHM18qKUojhvcokQsxQx5nMkelZq2n37zbSGCJn+FSODSjA==} + vite-tsconfig-paths@5.1.3: + resolution: {integrity: sha512-0bz+PDlLpGfP2CigeSKL9NFTF1KtXkeHGZSSaGQSuPZH77GhoiQaA8IjYgOaynSuwlDTolSUEU0ErVvju3NURg==} peerDependencies: vite: '*' peerDependenciesMeta: @@ -2666,15 +2699,15 @@ packages: terser: optional: true - vitest@2.1.5: - resolution: {integrity: sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==} + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.5 - '@vitest/ui': 2.1.5 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2691,10 +2724,6 @@ packages: jsdom: optional: true - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2732,8 +2761,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yauzl@3.2.0: + resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + engines: {node: '>=12'} zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -2744,11 +2774,11 @@ snapshots: optionalDependencies: graphql: 16.9.0 - '@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.6.3)': + '@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.7.2)': dependencies: - '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.6.3) + '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.7.2) graphql: 16.9.0 - typescript: 5.6.3 + typescript: 5.7.2 '@ampproject/remapping@2.3.0': dependencies: @@ -2759,11 +2789,11 @@ snapshots: '@babel/helper-validator-identifier@7.25.9': {} - '@babel/parser@7.26.2': + '@babel/parser@7.26.3': dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.26.3 - '@babel/types@7.26.0': + '@babel/types@7.26.3': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -3109,7 +3139,7 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@faker-js/faker@9.2.0': {} + '@faker-js/faker@9.3.0': {} '@fastify/accept-negotiator@2.0.0': {} @@ -3130,10 +3160,10 @@ snapshots: dependencies: fast-json-stringify: 6.0.0 - '@fastify/helmet@12.0.1': + '@fastify/helmet@13.0.0': dependencies: fastify-plugin: 5.0.1 - helmet: 7.2.0 + helmet: 8.0.0 '@fastify/jwt@9.0.1': dependencies: @@ -3153,7 +3183,7 @@ snapshots: fastify-plugin: 5.0.1 toad-cache: 3.7.0 - '@fastify/send@3.1.1': + '@fastify/send@3.3.0': dependencies: '@lukeed/ms': 2.0.2 escape-html: 1.0.3 @@ -3161,18 +3191,18 @@ snapshots: http-errors: 2.0.0 mime: 3.0.0 - '@fastify/static@8.0.2': + '@fastify/static@8.0.3': dependencies: '@fastify/accept-negotiator': 2.0.0 - '@fastify/send': 3.1.1 + '@fastify/send': 3.3.0 content-disposition: 0.5.4 fastify-plugin: 5.0.1 fastq: 1.17.1 glob: 11.0.0 - '@fastify/type-provider-typebox@5.0.1(@sinclair/typebox@0.34.3)': + '@fastify/type-provider-typebox@5.1.0(@sinclair/typebox@0.34.10)': dependencies: - '@sinclair/typebox': 0.34.3 + '@sinclair/typebox': 0.34.10 '@fastify/websocket@11.0.1': dependencies: @@ -3183,18 +3213,18 @@ snapshots: - bufferutil - utf-8-validate - '@gql.tada/cli-utils@1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.6.3))(graphql@16.9.0)(typescript@5.6.3)': + '@gql.tada/cli-utils@1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.7.2))(graphql@16.9.0)(typescript@5.7.2)': dependencies: - '@0no-co/graphqlsp': 1.12.16(graphql@16.9.0)(typescript@5.6.3) - '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.6.3) + '@0no-co/graphqlsp': 1.12.16(graphql@16.9.0)(typescript@5.7.2) + '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.7.2) graphql: 16.9.0 - typescript: 5.6.3 + typescript: 5.7.2 - '@gql.tada/internal@1.0.8(graphql@16.9.0)(typescript@5.6.3)': + '@gql.tada/internal@1.0.8(graphql@16.9.0)(typescript@5.7.2)': dependencies: '@0no-co/graphql.web': 1.0.11(graphql@16.9.0) graphql: 16.9.0 - typescript: 5.6.3 + typescript: 5.7.2 '@graphql-typed-document-node/core@3.2.0(graphql@16.9.0)': dependencies: @@ -3241,17 +3271,6 @@ snapshots: '@lukeed/ms@2.0.2': {} - '@mole-inc/bin-wrapper@8.0.1': - dependencies: - bin-check: 4.1.0 - bin-version-check: 5.1.0 - content-disposition: 0.5.4 - ext-name: 5.0.0 - file-type: 17.1.6 - filenamify: 5.1.1 - got: 11.8.6 - os-filter-obj: 2.0.0 - '@napi-rs/nice-android-arm-eabi@1.0.1': optional: true @@ -3407,130 +3426,137 @@ snapshots: dependencies: graphql: 16.9.0 - '@rollup/rollup-android-arm-eabi@4.27.2': + '@pothos/plugin-relay@4.3.0(@pothos/core@4.3.0(graphql@16.9.0))(graphql@16.9.0)': + dependencies: + '@pothos/core': 4.3.0(graphql@16.9.0) + graphql: 16.9.0 + + '@rollup/rollup-android-arm-eabi@4.28.0': optional: true - '@rollup/rollup-android-arm64@4.27.2': + '@rollup/rollup-android-arm64@4.28.0': optional: true - '@rollup/rollup-darwin-arm64@4.27.2': + '@rollup/rollup-darwin-arm64@4.28.0': optional: true - '@rollup/rollup-darwin-x64@4.27.2': + '@rollup/rollup-darwin-x64@4.28.0': optional: true - '@rollup/rollup-freebsd-arm64@4.27.2': + '@rollup/rollup-freebsd-arm64@4.28.0': optional: true - '@rollup/rollup-freebsd-x64@4.27.2': + '@rollup/rollup-freebsd-x64@4.28.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.27.2': + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.27.2': + '@rollup/rollup-linux-arm-musleabihf@4.28.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.27.2': + '@rollup/rollup-linux-arm64-gnu@4.28.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.27.2': + '@rollup/rollup-linux-arm64-musl@4.28.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.27.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.27.2': + '@rollup/rollup-linux-riscv64-gnu@4.28.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.27.2': + '@rollup/rollup-linux-s390x-gnu@4.28.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.27.2': + '@rollup/rollup-linux-x64-gnu@4.28.0': optional: true - '@rollup/rollup-linux-x64-musl@4.27.2': + '@rollup/rollup-linux-x64-musl@4.28.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.27.2': + '@rollup/rollup-win32-arm64-msvc@4.28.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.27.2': + '@rollup/rollup-win32-ia32-msvc@4.28.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.27.2': + '@rollup/rollup-win32-x64-msvc@4.28.0': optional: true - '@sinclair/typebox@0.34.3': {} + '@sec-ant/readable-stream@0.4.1': {} + + '@sinclair/typebox@0.34.10': {} - '@sindresorhus/is@4.6.0': {} + '@sindresorhus/is@5.6.0': {} - '@swc/cli@0.5.0(@swc/core@1.9.2)': + '@swc/cli@0.5.2(@swc/core@1.10.0)': dependencies: - '@mole-inc/bin-wrapper': 8.0.1 - '@swc/core': 1.9.2 + '@swc/core': 1.10.0 '@swc/counter': 0.1.3 + '@xhmikosr/bin-wrapper': 13.0.5 commander: 8.3.0 fast-glob: 3.3.2 minimatch: 9.0.5 - piscina: 4.7.0 + piscina: 4.8.0 semver: 7.6.3 slash: 3.0.0 source-map: 0.7.4 - '@swc/core-darwin-arm64@1.9.2': + '@swc/core-darwin-arm64@1.10.0': optional: true - '@swc/core-darwin-x64@1.9.2': + '@swc/core-darwin-x64@1.10.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.9.2': + '@swc/core-linux-arm-gnueabihf@1.10.0': optional: true - '@swc/core-linux-arm64-gnu@1.9.2': + '@swc/core-linux-arm64-gnu@1.10.0': optional: true - '@swc/core-linux-arm64-musl@1.9.2': + '@swc/core-linux-arm64-musl@1.10.0': optional: true - '@swc/core-linux-x64-gnu@1.9.2': + '@swc/core-linux-x64-gnu@1.10.0': optional: true - '@swc/core-linux-x64-musl@1.9.2': + '@swc/core-linux-x64-musl@1.10.0': optional: true - '@swc/core-win32-arm64-msvc@1.9.2': + '@swc/core-win32-arm64-msvc@1.10.0': optional: true - '@swc/core-win32-ia32-msvc@1.9.2': + '@swc/core-win32-ia32-msvc@1.10.0': optional: true - '@swc/core-win32-x64-msvc@1.9.2': + '@swc/core-win32-x64-msvc@1.10.0': optional: true - '@swc/core@1.9.2': + '@swc/core@1.10.0': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.15 + '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.9.2 - '@swc/core-darwin-x64': 1.9.2 - '@swc/core-linux-arm-gnueabihf': 1.9.2 - '@swc/core-linux-arm64-gnu': 1.9.2 - '@swc/core-linux-arm64-musl': 1.9.2 - '@swc/core-linux-x64-gnu': 1.9.2 - '@swc/core-linux-x64-musl': 1.9.2 - '@swc/core-win32-arm64-msvc': 1.9.2 - '@swc/core-win32-ia32-msvc': 1.9.2 - '@swc/core-win32-x64-msvc': 1.9.2 + '@swc/core-darwin-arm64': 1.10.0 + '@swc/core-darwin-x64': 1.10.0 + '@swc/core-linux-arm-gnueabihf': 1.10.0 + '@swc/core-linux-arm64-gnu': 1.10.0 + '@swc/core-linux-arm64-musl': 1.10.0 + '@swc/core-linux-x64-gnu': 1.10.0 + '@swc/core-linux-x64-musl': 1.10.0 + '@swc/core-win32-arm64-msvc': 1.10.0 + '@swc/core-win32-ia32-msvc': 1.10.0 + '@swc/core-win32-x64-msvc': 1.10.0 '@swc/counter@0.1.3': {} - '@swc/types@0.1.15': + '@swc/types@0.1.17': dependencies: '@swc/counter': 0.1.3 - '@szmarczak/http-timer@4.0.6': + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -3541,30 +3567,15 @@ snapshots: tslib: 2.8.1 optional: true - '@types/cacheable-request@6.0.3': - dependencies: - '@types/http-cache-semantics': 4.0.4 - '@types/keyv': 3.1.4 - '@types/node': 22.9.0 - '@types/responselike': 1.0.3 - '@types/estree@1.0.6': {} '@types/http-cache-semantics@4.0.4': {} - '@types/keyv@3.1.4': - dependencies: - '@types/node': 22.9.0 - - '@types/node@22.9.0': - dependencies: - undici-types: 6.19.8 - - '@types/responselike@1.0.3': + '@types/node@22.10.1': dependencies: - '@types/node': 22.9.0 + undici-types: 6.20.0 - '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.9.0))': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@22.10.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3573,55 +3584,123 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 - magic-string: 0.30.12 + magic-string: 0.30.14 magicast: 0.3.5 std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.5(@types/node@22.9.0) + vitest: 2.1.8(@types/node@22.10.1) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.5': + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 2.1.5 - '@vitest/utils': 2.1.5 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.9.0))': + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.1))': dependencies: - '@vitest/spy': 2.1.5 + '@vitest/spy': 2.1.8 estree-walker: 3.0.3 - magic-string: 0.30.12 + magic-string: 0.30.14 optionalDependencies: - vite: 5.4.11(@types/node@22.9.0) + vite: 5.4.11(@types/node@22.10.1) - '@vitest/pretty-format@2.1.5': + '@vitest/pretty-format@2.1.8': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.5': + '@vitest/runner@2.1.8': dependencies: - '@vitest/utils': 2.1.5 + '@vitest/utils': 2.1.8 pathe: 1.1.2 - '@vitest/snapshot@2.1.5': + '@vitest/snapshot@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.5 - magic-string: 0.30.12 + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.14 pathe: 1.1.2 - '@vitest/spy@2.1.5': + '@vitest/spy@2.1.8': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.5': + '@vitest/utils@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.5 + '@vitest/pretty-format': 2.1.8 loupe: 3.1.2 tinyrainbow: 1.2.0 + '@xhmikosr/archive-type@7.0.0': + dependencies: + file-type: 19.6.0 + + '@xhmikosr/bin-check@7.0.3': + dependencies: + execa: 5.1.1 + isexe: 2.0.0 + + '@xhmikosr/bin-wrapper@13.0.5': + dependencies: + '@xhmikosr/bin-check': 7.0.3 + '@xhmikosr/downloader': 15.0.1 + '@xhmikosr/os-filter-obj': 3.0.0 + bin-version-check: 5.1.0 + + '@xhmikosr/decompress-tar@8.0.1': + dependencies: + file-type: 19.6.0 + is-stream: 2.0.1 + tar-stream: 3.1.7 + + '@xhmikosr/decompress-tarbz2@8.0.1': + dependencies: + '@xhmikosr/decompress-tar': 8.0.1 + file-type: 19.6.0 + is-stream: 2.0.1 + seek-bzip: 2.0.0 + unbzip2-stream: 1.4.3 + + '@xhmikosr/decompress-targz@8.0.1': + dependencies: + '@xhmikosr/decompress-tar': 8.0.1 + file-type: 19.6.0 + is-stream: 2.0.1 + + '@xhmikosr/decompress-unzip@7.0.0': + dependencies: + file-type: 19.6.0 + get-stream: 6.0.1 + yauzl: 3.2.0 + + '@xhmikosr/decompress@10.0.1': + dependencies: + '@xhmikosr/decompress-tar': 8.0.1 + '@xhmikosr/decompress-tarbz2': 8.0.1 + '@xhmikosr/decompress-targz': 8.0.1 + '@xhmikosr/decompress-unzip': 7.0.0 + graceful-fs: 4.2.11 + make-dir: 4.0.0 + strip-dirs: 3.0.0 + + '@xhmikosr/downloader@15.0.1': + dependencies: + '@xhmikosr/archive-type': 7.0.0 + '@xhmikosr/decompress': 10.0.1 + content-disposition: 0.5.4 + defaults: 3.0.0 + ext-name: 5.0.0 + file-type: 19.6.0 + filenamify: 6.0.0 + get-stream: 6.0.1 + got: 13.0.0 + + '@xhmikosr/os-filter-obj@3.0.0': + dependencies: + arch: 3.0.0 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -3654,7 +3733,7 @@ snapshots: ansi-styles@6.2.1: {} - arch@2.2.0: {} + arch@3.0.0: {} asn1.js@5.4.1: dependencies: @@ -3672,14 +3751,14 @@ snapshots: '@fastify/error': 4.0.0 fastq: 1.17.1 + b4a@1.6.7: {} + balanced-match@1.0.2: {} - base64-js@1.5.1: {} + bare-events@2.5.0: + optional: true - bin-check@4.1.0: - dependencies: - execa: 0.7.0 - executable: 4.1.1 + base64-js@1.5.1: {} bin-version-check@5.1.0: dependencies: @@ -3702,8 +3781,15 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -3711,17 +3797,17 @@ snapshots: cac@6.7.14: {} - cacheable-lookup@5.0.4: {} + cacheable-lookup@7.0.0: {} - cacheable-request@7.0.4: + cacheable-request@10.2.14: dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 + '@types/http-cache-semantics': 4.0.4 + get-stream: 6.0.1 http-cache-semantics: 4.1.1 keyv: 4.5.4 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 chai@5.1.2: dependencies: @@ -3735,10 +3821,6 @@ snapshots: clean-stack@2.2.0: {} - clone-response@1.0.3: - dependencies: - mimic-response: 1.0.1 - close-with-grace@2.1.0: {} color-convert@2.0.1: @@ -3749,6 +3831,8 @@ snapshots: colorette@2.0.20: {} + commander@6.2.1: {} + commander@8.3.0: {} content-disposition@0.5.4: @@ -3757,13 +3841,7 @@ snapshots: cookie@0.5.0: {} - cookie@1.0.1: {} - - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 + cookie@1.0.2: {} cross-spawn@7.0.6: dependencies: @@ -3783,15 +3861,17 @@ snapshots: deep-eql@5.0.2: {} + defaults@3.0.0: {} + defer-to-connect@2.0.1: {} depd@2.0.0: {} dotenv-expand@10.0.0: {} - dotenv@16.4.5: {} + dotenv@16.4.7: {} - drizzle-kit@0.28.1: + drizzle-kit@0.29.1: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 @@ -3800,14 +3880,14 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.36.3(@libsql/client-wasm@0.14.0)(postgres@3.4.5): + drizzle-orm@0.37.0(@libsql/client-wasm@0.14.0)(postgres@3.4.5): optionalDependencies: '@libsql/client-wasm': 0.14.0 postgres: 3.4.5 - drizzle-zod@0.5.1(drizzle-orm@0.36.3(@libsql/client-wasm@0.14.0)(postgres@3.4.5))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.37.0(@libsql/client-wasm@0.14.0)(postgres@3.4.5))(zod@3.23.8): dependencies: - drizzle-orm: 0.36.3(@libsql/client-wasm@0.14.0)(postgres@3.4.5) + drizzle-orm: 0.37.0(@libsql/client-wasm@0.14.0)(postgres@3.4.5) zod: 3.23.8 duplexify@4.1.3: @@ -3834,7 +3914,7 @@ snapshots: env-schema@6.0.0: dependencies: ajv: 8.17.1 - dotenv: 16.4.5 + dotenv: 16.4.7 dotenv-expand: 10.0.0 es-module-lexer@1.5.4: {} @@ -3952,8 +4032,6 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@5.0.0: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.6 @@ -3962,16 +4040,6 @@ snapshots: events@3.3.0: {} - execa@0.7.0: - dependencies: - cross-spawn: 5.1.0 - get-stream: 3.0.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -3984,10 +4052,6 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - executable@4.1.1: - dependencies: - pify: 2.3.0 - expect-type@1.1.0: {} ext-list@2.2.2: @@ -4005,6 +4069,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4090,19 +4156,18 @@ snapshots: reusify: 1.0.4 xtend: 4.0.2 - file-type@17.1.6: + file-type@19.6.0: dependencies: - readable-web-to-node-stream: 3.0.2 - strtok3: 7.1.1 - token-types: 5.0.1 + get-stream: 9.0.1 + strtok3: 9.1.1 + token-types: 6.0.0 + uint8array-extras: 1.4.0 filename-reserved-regex@3.0.0: {} - filenamify@5.1.1: + filenamify@6.0.0: dependencies: filename-reserved-regex: 3.0.0 - strip-outer: 2.0.0 - trim-repeated: 2.0.0 fill-range@7.1.1: dependencies: @@ -4123,6 +4188,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data-encoder@2.1.4: {} + forwarded@0.2.0: {} fsevents@2.3.3: @@ -4132,13 +4199,12 @@ snapshots: dependencies: is-property: 1.0.2 - get-stream@3.0.0: {} + get-stream@6.0.1: {} - get-stream@5.2.0: + get-stream@9.0.1: dependencies: - pump: 3.0.2 - - get-stream@6.0.1: {} + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 get-tsconfig@4.8.1: dependencies: @@ -4168,32 +4234,34 @@ snapshots: globrex@0.1.2: {} - got@11.8.6: + got@13.0.0: dependencies: - '@sindresorhus/is': 4.6.0 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.3 - cacheable-lookup: 5.0.4 - cacheable-request: 7.0.4 + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 decompress-response: 6.0.0 - http2-wrapper: 1.0.3 - lowercase-keys: 2.0.0 - p-cancelable: 2.1.1 - responselike: 2.0.1 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 - gql.tada@1.8.10(graphql@16.9.0)(typescript@5.6.3): + gql.tada@1.8.10(graphql@16.9.0)(typescript@5.7.2): dependencies: '@0no-co/graphql.web': 1.0.11(graphql@16.9.0) - '@0no-co/graphqlsp': 1.12.16(graphql@16.9.0)(typescript@5.6.3) - '@gql.tada/cli-utils': 1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.6.3))(graphql@16.9.0)(typescript@5.6.3) - '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.6.3) - typescript: 5.6.3 + '@0no-co/graphqlsp': 1.12.16(graphql@16.9.0)(typescript@5.7.2) + '@gql.tada/cli-utils': 1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.9.0)(typescript@5.7.2))(graphql@16.9.0)(typescript@5.7.2) + '@gql.tada/internal': 1.0.8(graphql@16.9.0)(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - '@gql.tada/svelte-support' - '@gql.tada/vue-support' - graphql + graceful-fs@4.2.11: {} + graphql-jit@0.8.6(graphql@16.9.0): dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) @@ -4204,7 +4272,7 @@ snapshots: lodash.merge: 4.6.2 lodash.mergewith: 4.6.2 - graphql-scalars@1.23.0(graphql@16.9.0): + graphql-scalars@1.24.0(graphql@16.9.0): dependencies: graphql: 16.9.0 tslib: 2.8.1 @@ -4213,7 +4281,7 @@ snapshots: has-flag@4.0.0: {} - helmet@7.2.0: {} + helmet@8.0.0: {} help-me@5.0.0: {} @@ -4229,7 +4297,7 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - http2-wrapper@1.0.3: + http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 @@ -4242,6 +4310,10 @@ snapshots: inherits@2.0.4: {} + inspect-with-kind@1.0.5: + dependencies: + kind-of: 6.0.3 + ipaddr.js@1.9.1: {} is-extglob@2.1.1: {} @@ -4258,10 +4330,10 @@ snapshots: is-property@1.0.2: {} - is-stream@1.1.0: {} - is-stream@2.0.1: {} + is-stream@4.0.1: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -4312,52 +4384,54 @@ snapshots: dependencies: json-buffer: 3.0.1 - lefthook-darwin-arm64@1.8.4: + kind-of@6.0.3: {} + + lefthook-darwin-arm64@1.8.5: optional: true - lefthook-darwin-x64@1.8.4: + lefthook-darwin-x64@1.8.5: optional: true - lefthook-freebsd-arm64@1.8.4: + lefthook-freebsd-arm64@1.8.5: optional: true - lefthook-freebsd-x64@1.8.4: + lefthook-freebsd-x64@1.8.5: optional: true - lefthook-linux-arm64@1.8.4: + lefthook-linux-arm64@1.8.5: optional: true - lefthook-linux-x64@1.8.4: + lefthook-linux-x64@1.8.5: optional: true - lefthook-openbsd-arm64@1.8.4: + lefthook-openbsd-arm64@1.8.5: optional: true - lefthook-openbsd-x64@1.8.4: + lefthook-openbsd-x64@1.8.5: optional: true - lefthook-windows-arm64@1.8.4: + lefthook-windows-arm64@1.8.5: optional: true - lefthook-windows-x64@1.8.4: + lefthook-windows-x64@1.8.5: optional: true - lefthook@1.8.4: + lefthook@1.8.5: optionalDependencies: - lefthook-darwin-arm64: 1.8.4 - lefthook-darwin-x64: 1.8.4 - lefthook-freebsd-arm64: 1.8.4 - lefthook-freebsd-x64: 1.8.4 - lefthook-linux-arm64: 1.8.4 - lefthook-linux-x64: 1.8.4 - lefthook-openbsd-arm64: 1.8.4 - lefthook-openbsd-x64: 1.8.4 - lefthook-windows-arm64: 1.8.4 - lefthook-windows-x64: 1.8.4 + lefthook-darwin-arm64: 1.8.5 + lefthook-darwin-x64: 1.8.5 + lefthook-freebsd-arm64: 1.8.5 + lefthook-freebsd-x64: 1.8.5 + lefthook-linux-arm64: 1.8.5 + lefthook-linux-x64: 1.8.5 + lefthook-openbsd-arm64: 1.8.5 + lefthook-openbsd-x64: 1.8.5 + lefthook-windows-arm64: 1.8.5 + lefthook-windows-x64: 1.8.5 light-my-request@6.3.0: dependencies: - cookie: 1.0.1 + cookie: 1.0.2 process-warning: 4.0.0 set-cookie-parser: 2.7.1 @@ -4369,25 +4443,20 @@ snapshots: loupe@3.1.2: {} - lowercase-keys@2.0.0: {} + lowercase-keys@3.0.0: {} lru-cache@10.4.3: {} lru-cache@11.0.2: {} - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - - magic-string@0.30.12: + magic-string@0.30.14: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 magicast@0.3.5: dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 source-map-js: 1.2.1 make-dir@4.0.0: @@ -4409,7 +4478,7 @@ snapshots: mercurius@15.1.0(graphql@16.9.0): dependencies: '@fastify/error': 4.0.0 - '@fastify/static': 8.0.2 + '@fastify/static': 8.0.3 '@fastify/websocket': 11.0.1 fastify-plugin: 5.0.1 graphql: 16.9.0 @@ -4443,10 +4512,10 @@ snapshots: mimic-fn@2.1.0: {} - mimic-response@1.0.1: {} - mimic-response@3.1.0: {} + mimic-response@4.0.0: {} + minimalistic-assert@1.0.1: {} minimatch@10.0.1: @@ -4472,13 +4541,9 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.7: {} + nanoid@3.3.8: {} - normalize-url@6.1.0: {} - - npm-run-path@2.0.2: - dependencies: - path-key: 2.0.1 + normalize-url@8.0.1: {} npm-run-path@4.0.1: dependencies: @@ -4496,13 +4561,7 @@ snapshots: dependencies: mimic-fn: 2.1.0 - os-filter-obj@2.0.0: - dependencies: - arch: 2.2.0 - - p-cancelable@2.1.1: {} - - p-finally@1.0.0: {} + p-cancelable@3.0.0: {} p-map@4.0.0: dependencies: @@ -4510,8 +4569,6 @@ snapshots: package-json-from-dist@1.0.1: {} - path-key@2.0.1: {} - path-key@3.1.1: {} path-scurry@1.11.1: @@ -4530,12 +4587,12 @@ snapshots: peek-readable@5.3.1: {} + pend@1.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} - pify@2.3.0: {} - pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 @@ -4572,13 +4629,13 @@ snapshots: sonic-boom: 4.2.0 thread-stream: 3.1.0 - piscina@4.7.0: + piscina@4.8.0: optionalDependencies: '@napi-rs/nice': 1.0.1 postcss@8.4.49: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -4593,8 +4650,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - pseudomap@1.0.2: {} - pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -4604,6 +4659,8 @@ snapshots: queue-microtask@1.2.3: {} + queue-tick@1.0.1: {} + quick-format-unescaped@4.0.4: {} quick-lru@5.1.1: {} @@ -4624,10 +4681,6 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 - readable-web-to-node-stream@3.0.2: - dependencies: - readable-stream: 3.6.2 - real-require@0.2.0: {} require-from-string@2.0.2: {} @@ -4636,9 +4689,9 @@ snapshots: resolve-pkg-maps@1.0.0: {} - responselike@2.0.1: + responselike@3.0.0: dependencies: - lowercase-keys: 2.0.0 + lowercase-keys: 3.0.0 ret@0.5.0: {} @@ -4646,28 +4699,28 @@ snapshots: rfdc@1.4.1: {} - rollup@4.27.2: + rollup@4.28.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.27.2 - '@rollup/rollup-android-arm64': 4.27.2 - '@rollup/rollup-darwin-arm64': 4.27.2 - '@rollup/rollup-darwin-x64': 4.27.2 - '@rollup/rollup-freebsd-arm64': 4.27.2 - '@rollup/rollup-freebsd-x64': 4.27.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.27.2 - '@rollup/rollup-linux-arm-musleabihf': 4.27.2 - '@rollup/rollup-linux-arm64-gnu': 4.27.2 - '@rollup/rollup-linux-arm64-musl': 4.27.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.27.2 - '@rollup/rollup-linux-riscv64-gnu': 4.27.2 - '@rollup/rollup-linux-s390x-gnu': 4.27.2 - '@rollup/rollup-linux-x64-gnu': 4.27.2 - '@rollup/rollup-linux-x64-musl': 4.27.2 - '@rollup/rollup-win32-arm64-msvc': 4.27.2 - '@rollup/rollup-win32-ia32-msvc': 4.27.2 - '@rollup/rollup-win32-x64-msvc': 4.27.2 + '@rollup/rollup-android-arm-eabi': 4.28.0 + '@rollup/rollup-android-arm64': 4.28.0 + '@rollup/rollup-darwin-arm64': 4.28.0 + '@rollup/rollup-darwin-x64': 4.28.0 + '@rollup/rollup-freebsd-arm64': 4.28.0 + '@rollup/rollup-freebsd-x64': 4.28.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.0 + '@rollup/rollup-linux-arm-musleabihf': 4.28.0 + '@rollup/rollup-linux-arm64-gnu': 4.28.0 + '@rollup/rollup-linux-arm64-musl': 4.28.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0 + '@rollup/rollup-linux-riscv64-gnu': 4.28.0 + '@rollup/rollup-linux-s390x-gnu': 4.28.0 + '@rollup/rollup-linux-x64-gnu': 4.28.0 + '@rollup/rollup-linux-x64-musl': 4.28.0 + '@rollup/rollup-win32-arm64-msvc': 4.28.0 + '@rollup/rollup-win32-ia32-msvc': 4.28.0 + '@rollup/rollup-win32-x64-msvc': 4.28.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4686,6 +4739,10 @@ snapshots: secure-json-parse@2.7.0: {} + seek-bzip@2.0.0: + dependencies: + commander: 6.2.1 + semver-regex@4.0.5: {} semver-truncate@3.0.0: @@ -4698,16 +4755,10 @@ snapshots: setprototypeof@1.2.0: {} - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} siginfo@2.0.0: {} @@ -4763,6 +4814,14 @@ snapshots: stream-shift@1.0.3: {} + streamx@2.21.0: + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.2.1 + optionalDependencies: + bare-events: 2.5.0 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4787,15 +4846,16 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-eof@1.0.0: {} + strip-dirs@3.0.0: + dependencies: + inspect-with-kind: 1.0.5 + is-plain-obj: 1.1.0 strip-final-newline@2.0.0: {} strip-json-comments@3.1.1: {} - strip-outer@2.0.0: {} - - strtok3@7.1.1: + strtok3@9.1.1: dependencies: '@tokenizer/token': 0.3.0 peek-readable: 5.3.1 @@ -4804,16 +4864,26 @@ snapshots: dependencies: has-flag: 4.0.0 + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.21.0 + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 glob: 10.4.5 minimatch: 9.0.5 + text-decoder@1.2.1: {} + thread-stream@3.1.0: dependencies: real-require: 0.2.0 + through@2.3.8: {} + tiny-lru@11.2.11: {} tinybench@2.9.0: {} @@ -4834,18 +4904,14 @@ snapshots: toidentifier@1.0.1: {} - token-types@5.0.1: + token-types@6.0.0: dependencies: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - trim-repeated@2.0.0: - dependencies: - escape-string-regexp: 5.0.0 - - tsconfck@3.1.4(typescript@5.6.3): + tsconfck@3.1.4(typescript@5.7.2): optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 tslib@2.8.1: {} @@ -4856,9 +4922,16 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - typescript@5.6.3: {} + typescript@5.7.2: {} - undici-types@6.19.8: {} + uint8array-extras@1.4.0: {} + + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + + undici-types@6.20.0: {} undici@6.21.0: {} @@ -4866,13 +4939,13 @@ snapshots: uuidv7@1.0.2: {} - vite-node@2.1.5(@types/node@22.9.0): + vite-node@2.1.8(@types/node@22.10.1): dependencies: cac: 6.7.14 debug: 4.3.7 es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.11(@types/node@22.9.0) + vite: 5.4.11(@types/node@22.10.1) transitivePeerDependencies: - '@types/node' - less @@ -4884,50 +4957,50 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)): + vite-tsconfig-paths@5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)): dependencies: debug: 4.3.7 globrex: 0.1.2 - tsconfck: 3.1.4(typescript@5.6.3) + tsconfck: 3.1.4(typescript@5.7.2) optionalDependencies: - vite: 5.4.11(@types/node@22.9.0) + vite: 5.4.11(@types/node@22.10.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.11(@types/node@22.9.0): + vite@5.4.11(@types/node@22.10.1): dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.27.2 + rollup: 4.28.0 optionalDependencies: - '@types/node': 22.9.0 + '@types/node': 22.10.1 fsevents: 2.3.3 - vitest@2.1.5(@types/node@22.9.0): + vitest@2.1.8(@types/node@22.10.1): dependencies: - '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.9.0)) - '@vitest/pretty-format': 2.1.5 - '@vitest/runner': 2.1.5 - '@vitest/snapshot': 2.1.5 - '@vitest/spy': 2.1.5 - '@vitest/utils': 2.1.5 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 debug: 4.3.7 expect-type: 1.1.0 - magic-string: 0.30.12 + magic-string: 0.30.14 pathe: 1.1.2 std-env: 3.8.0 tinybench: 2.9.0 tinyexec: 0.3.1 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.9.0) - vite-node: 2.1.5(@types/node@22.9.0) + vite: 5.4.11(@types/node@22.10.1) + vite-node: 2.1.8(@types/node@22.10.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.9.0 + '@types/node': 22.10.1 transitivePeerDependencies: - less - lightningcss @@ -4939,10 +5012,6 @@ snapshots: - supports-color - terser - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -4970,6 +5039,9 @@ snapshots: xtend@4.0.2: {} - yallist@2.1.2: {} + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 zod@3.23.8: {} diff --git a/schema.graphql b/schema.graphql index bb7cb804cc..9268a1b548 100644 --- a/schema.graphql +++ b/schema.graphql @@ -278,6 +278,9 @@ enum Iso3166Alpha2CountryCode { } type Mutation { + """Mutation field to create an organization.""" + createOrganization(input: MutationCreateOrganizationInput!): Organization + """Mutation field to create a user.""" createUser( """Input required to create a user.""" @@ -287,6 +290,9 @@ type Mutation { """Mutation field to delete the current user.""" deleteCurrentUser: User + """Mutation field to delete an organization.""" + deleteOrganization(input: MutationDeleteOrganizationInput!): Organization + """Mutation field to delete a user.""" deleteUser( """Input required to delete a user.""" @@ -305,6 +311,9 @@ type Mutation { input: MutationUpdateCurrentUserInput! ): User + """Mutation field to update a organization.""" + updateOrganization(input: MutationUpdateOrganizationInput!): Organization + """Mutation field to update a user.""" updateUser( """Input required to update a user.""" @@ -312,6 +321,33 @@ type Mutation { ): User } +"""""" +input MutationCreateOrganizationInput { + """Address of the organization.""" + address: String + + """URI to the avatar of the organization.""" + avatarURI: String + + """Name of the city where the organization resides in.""" + city: String + + """Country code of the country the organization is a citizen of.""" + countryCode: Iso3166Alpha2CountryCode + + """Custom information about the organization.""" + description: String + + """Name of the organization.""" + name: String! + + """Postal code of the organization.""" + postalCode: String + + """Name of the state the organization resides in.""" + state: String +} + """""" input MutationCreateUserInput { """Address of the user.""" @@ -379,6 +415,12 @@ input MutationCreateUserInput { workPhoneNumber: PhoneNumber } +"""""" +input MutationDeleteOrganizationInput { + """Global identifier of the organization.""" + id: ID! +} + """""" input MutationDeleteUserInput { """Global identifier of the user.""" @@ -510,6 +552,36 @@ input MutationUpdateCurrentUserInput { workPhoneNumber: PhoneNumber } +"""""" +input MutationUpdateOrganizationInput { + """Address of the organization.""" + address: String + + """URI to the avatar of the organization.""" + avatarURI: String + + """Name of the city where the organization resides in.""" + city: String + + """Country code of the country the organization is a citizen of.""" + countryCode: Iso3166Alpha2CountryCode + + """Custom information about the organization.""" + description: String + + """Global identifier of the organization.""" + id: ID! + + """Name of the organization.""" + name: String + + """Postal code of the organization.""" + postalCode: String + + """Name of the state the organization resides in.""" + state: String +} + """""" input MutationUpdateUserInput { """Address of the user.""" @@ -580,6 +652,47 @@ input MutationUpdateUserInput { workPhoneNumber: PhoneNumber } +type Organization { + """Address of the organization.""" + address: String + + """URI to the avatar of the organization.""" + avatarURI: String + + """Name of the city where the organization exists in.""" + city: String + + """Country code of the country the organization is a citizen of.""" + countryCode: Iso3166Alpha2CountryCode + + """Date time at the time the organization was created.""" + createdAt: DateTime + + """User who created the organization.""" + creator: User + + """Custom information about the organization.""" + description: String + + """Global identifier of the organization.""" + id: ID! + + """Name of the organization.""" + name: String + + """Postal code of the organization.""" + postalCode: String + + """Name of the state the organization exists in.""" + state: String + + """Date time at the time the organization was last updated.""" + updatedAt: DateTime + + """User who last updated the organization.""" + updater: User +} + """ A field whose value conforms to the standard E.164 format as specified in: https://en.wikipedia.org/wiki/E.164. Basically this is +17895551234. """ @@ -589,6 +702,12 @@ type Query { """Query field to read a user.""" currentUser: User + """Query field to read an organization.""" + organization( + """Input required to read an organization.""" + input: QueryOrganizationInput! + ): Organization + """ Query field to renew the authentication token of an authenticated client for signing in to talawa. """ @@ -607,6 +726,12 @@ type Query { ): User } +"""""" +input QueryOrganizationInput { + """Global id of the organization.""" + id: String! +} + """""" input QuerySignInInput { """Email address of the user.""" @@ -638,10 +763,10 @@ type User { """Country code of the country the user is a citizen of.""" countryCode: Iso3166Alpha2CountryCode - """Datetime at the time the user was created.""" + """Date time at the time the user was created.""" createdAt: DateTime - """User field to read the user who created the user.""" + """User who created the user.""" creator: User """Custom information about the user.""" @@ -688,10 +813,10 @@ type User { """Name of the state the user resides in.""" state: String - """Datetime at the time the user was last updated.""" + """Date time at the time the user was last updated.""" updatedAt: DateTime - """User field to read the user who last updated the user.""" + """User who last updated the user.""" updater: User """ @@ -747,5 +872,5 @@ enum UserNatalSex { """""" enum UserRole { administrator - base + regular } \ No newline at end of file diff --git a/scripts/generateGraphQLSDLFile.ts b/scripts/generateGraphQLSDLFile.ts index 076a521fe0..b2f2b1ffd1 100644 --- a/scripts/generateGraphQLSDLFile.ts +++ b/scripts/generateGraphQLSDLFile.ts @@ -1,6 +1,4 @@ -/** - * THIS SCRIPT IS MEANT FOR GENERATING THE TALAWA API GRAPHQL SCHEMA IN THE GRAPHQL SDL(SCHEMA DEFINITION LANGUAGE) FORMAT AT THE ROOT DIRECTORY OF THIS PROJECT IN A FILE NAMED `schema.graphql`. - */ +// THIS SCRIPT IS MEANT FOR GENERATING THE TALAWA API GRAPHQL SCHEMA IN THE GRAPHQL SDL(SCHEMA DEFINITION LANGUAGE) FORMAT AT THE ROOT DIRECTORY OF THIS PROJECT IN A FILE NAMED `schema.graphql`. import { writeFile } from "node:fs/promises"; import { lexicographicSortSchema, printSchema } from "graphql"; diff --git a/src/drizzle/enums/organizationMembershipRole.ts b/src/drizzle/enums/organizationMembershipRole.ts index 4d52ac58c9..e5f56e6f7f 100644 --- a/src/drizzle/enums/organizationMembershipRole.ts +++ b/src/drizzle/enums/organizationMembershipRole.ts @@ -5,5 +5,5 @@ import { pgEnum } from "drizzle-orm/pg-core"; */ export const organizationMembershipRoleEnum = pgEnum( "organization_membership_role", - ["administrator", "base"], + ["administrator", "regular"], ); diff --git a/src/drizzle/enums/userRole.ts b/src/drizzle/enums/userRole.ts index 9e44b424eb..af8d64ff24 100644 --- a/src/drizzle/enums/userRole.ts +++ b/src/drizzle/enums/userRole.ts @@ -3,4 +3,4 @@ import { pgEnum } from "drizzle-orm/pg-core"; /** * Possible variants of the role assigned to a user. */ -export const userRoleEnum = pgEnum("user_role", ["administrator", "base"]); +export const userRoleEnum = pgEnum("user_role", ["administrator", "regular"]); diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index 04a5936f60..bd3b8b5cc4 100755 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -89,7 +89,6 @@ export { tagAssignmentsTable, tagAssignmentsTableRelations, } from "./tables/tagAssignments"; -export { tagFoldersTable, tagFoldersTableRelations } from "./tables/tagFolders"; export { tagsTable, tagsTableRelations } from "./tables/tags"; export { usersTable, usersTableRelations } from "./tables/users"; export { diff --git a/src/drizzle/tables/actionCategories.ts b/src/drizzle/tables/actionCategories.ts index 70d5cd3bf1..6cc45f2348 100755 --- a/src/drizzle/tables/actionCategories.ts +++ b/src/drizzle/tables/actionCategories.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { boolean, index, @@ -44,7 +44,9 @@ export const actionCategoriesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/actions.ts b/src/drizzle/tables/actions.ts index f6e01ae493..6537e90c74 100755 --- a/src/drizzle/tables/actions.ts +++ b/src/drizzle/tables/actions.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { boolean, index, @@ -62,7 +62,9 @@ export const actionsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/advertisementAttachments.ts b/src/drizzle/tables/advertisementAttachments.ts index 091d8e5779..7c6c9a3691 100755 --- a/src/drizzle/tables/advertisementAttachments.ts +++ b/src/drizzle/tables/advertisementAttachments.ts @@ -1,13 +1,6 @@ -import { relations } from "drizzle-orm"; -import { - index, - integer, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { advertisementAttachmentTypeEnum } from "~/src/drizzle/enums/advertisementAttachmentType"; import { advertisementsTable } from "./advertisements"; import { usersTable } from "./users"; @@ -15,10 +8,15 @@ import { usersTable } from "./users"; export const advertisementAttachmentsTable = pgTable( "advertisement_attachments", { + /** + * Foreign key reference to the id of the advertisement that the attachment is associated to. + */ advertisementId: uuid("advertisement_id") .notNull() .references(() => advertisementsTable.id), - + /** + * Date time at the time the attachment was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -26,49 +24,66 @@ export const advertisementAttachmentsTable = pgTable( }) .notNull() .defaultNow(), - + /** + * Foreign key reference to the id of the user who first created the attachment. + */ creatorId: uuid("creator_id") .references(() => usersTable.id, {}) .notNull(), - position: integer("position").notNull(), - + /** + * Type of the attachment. + */ + type: advertisementAttachmentTypeEnum("type").notNull(), + /** + * Date time at the time the attachment was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), - + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the attachment. + */ updaterId: uuid("updater_id").references(() => usersTable.id, {}), - + /** + * URI to the attachment. + */ uri: text("uri", {}).notNull(), - - type: advertisementAttachmentTypeEnum("type").notNull(), }, (self) => [ index().on(self.advertisementId), index().on(self.createdAt), index().on(self.creatorId), - uniqueIndex().on(self.advertisementId, self.position), ], ); export const advertisementAttachmentsTableRelations = relations( advertisementAttachmentsTable, ({ one }) => ({ + /** + * Many to one relationship from `advertisement_attachments` table to `advertisements` table. + */ advertisement: one(advertisementsTable, { fields: [advertisementAttachmentsTable.advertisementId], references: [advertisementsTable.id], relationName: "advertisement_attachments.advertisement_id:advertisements.id", }), - + /** + * Many to one relationship from `advertisement_attachments` table to `users` table. + */ creator: one(usersTable, { fields: [advertisementAttachmentsTable.creatorId], references: [usersTable.id], relationName: "advertisement_attachments.creator_id:users.id", }), - + /** + * Many to one relationship from `advertisement_attachments` table to `users` table. + */ updater: one(usersTable, { fields: [advertisementAttachmentsTable.updaterId], references: [usersTable.id], @@ -76,3 +91,10 @@ export const advertisementAttachmentsTableRelations = relations( }), }), ); + +export const advertisementAttachmentsTableInsertSchema = createInsertSchema( + advertisementAttachmentsTable, + { + uri: (schema) => schema.uri.min(1), + }, +); diff --git a/src/drizzle/tables/advertisements.ts b/src/drizzle/tables/advertisements.ts index 973d4de409..f0cddd940d 100755 --- a/src/drizzle/tables/advertisements.ts +++ b/src/drizzle/tables/advertisements.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -7,6 +7,7 @@ import { uniqueIndex, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { uuidv7 } from "uuidv7"; import { advertisementTypeEnum } from "~/src/drizzle/enums/advertisementType"; import { advertisementAttachmentsTable } from "./advertisementAttachments"; @@ -16,6 +17,9 @@ import { usersTable } from "./users"; export const advertisementsTable = pgTable( "advertisements", { + /** + * Date time at the time the advertisement was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -23,45 +27,73 @@ export const advertisementsTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - + /** + * Foreign key reference to the id of the user who first created the advertisement. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Custom information about the advertisement. + */ description: text("description"), - + /** + * Date time at the time the advertised event ends at. + */ endAt: timestamp("end_at", { mode: "date", precision: 3, withTimezone: true, }).notNull(), - + /** + * Primary unique identifier of the advertisement. + */ id: uuid("id").primaryKey().$default(uuidv7), - + /** + * Name of the advertisement. + */ name: text("name", {}).notNull(), - + /** + * Foreign key reference to the id of the organization in which the advertisement is made. + */ organizationId: uuid("organization_id") .notNull() - .references(() => organizationsTable.id), - + .references(() => organizationsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the advertised event starts at. + */ startAt: timestamp("start_at", { mode: "date", precision: 3, withTimezone: true, }).notNull(), - + /** + * Date time at the time the advertisement was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the advertisement. + */ + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updater_id").references(() => usersTable.id, {}), - + /** + * Type of the attachment. + */ type: advertisementTypeEnum("type").notNull(), }, (self) => [ - index().on(self.createdAt), index().on(self.creatorId), index().on(self.endAt), index().on(self.name), @@ -74,6 +106,9 @@ export const advertisementsTable = pgTable( export const advertisementsTableRelations = relations( advertisementsTable, ({ many, one }) => ({ + /** + * One to many relationship from `advertisements` table to `advertisement_attachments` table. + */ advertisementAttachmentsWhereAdvertisement: many( advertisementAttachmentsTable, { @@ -81,19 +116,25 @@ export const advertisementsTableRelations = relations( "advertisement_attachments.advertisement_id:advertisements.id", }, ), - + /** + * Many to one relationship from `advertisements` table to `users` table. + */ creator: one(usersTable, { fields: [advertisementsTable.creatorId], references: [usersTable.id], relationName: "advertisements.creator_id:users.id", }), - + /** + * Many to one relationship from `advertisements` table to `organizations` table. + */ organization: one(organizationsTable, { fields: [advertisementsTable.organizationId], references: [organizationsTable.id], relationName: "advertisements.organization_id:organizations.id", }), - + /** + * Many to one relationship from `advertisements` table to `users` table. + */ updater: one(usersTable, { fields: [advertisementsTable.updaterId], references: [usersTable.id], @@ -101,3 +142,11 @@ export const advertisementsTableRelations = relations( }), }), ); + +export const advertisementsTableInsertSchema = createInsertSchema( + advertisementsTable, + { + description: (schema) => schema.description.min(1).max(2048), + name: (schema) => schema.name.min(1).max(256), + }, +); diff --git a/src/drizzle/tables/agendaItems.ts b/src/drizzle/tables/agendaItems.ts index 9d37784ded..e0e5884d33 100755 --- a/src/drizzle/tables/agendaItems.ts +++ b/src/drizzle/tables/agendaItems.ts @@ -42,7 +42,9 @@ export const agendaItemsTable = pgTable( key: text("key"), name: text("name", {}), - + /** + * Position of the agenda item relative to other agenda item associated to the same agenda section the agenda item is associated to. + */ position: integer("position").notNull(), sectionId: uuid("section_id") @@ -55,7 +57,9 @@ export const agendaItemsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/agendaSections.ts b/src/drizzle/tables/agendaSections.ts index 3f9c6714ee..c9d326b214 100755 --- a/src/drizzle/tables/agendaSections.ts +++ b/src/drizzle/tables/agendaSections.ts @@ -1,8 +1,7 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { type AnyPgColumn, index, - integer, pgTable, text, timestamp, @@ -41,13 +40,13 @@ export const agendaSectionsTable = pgTable( (): AnyPgColumn => agendaSectionsTable.id, ), - position: integer("position").notNull(), - updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, @@ -58,7 +57,6 @@ export const agendaSectionsTable = pgTable( index().on(self.name), index().on(self.parentSectionId), uniqueIndex().on(self.eventId, self.name), - uniqueIndex().on(self.eventId, self.position), ], ); diff --git a/src/drizzle/tables/commentVotes.ts b/src/drizzle/tables/commentVotes.ts index 3f11f677e6..78b2a7c859 100755 --- a/src/drizzle/tables/commentVotes.ts +++ b/src/drizzle/tables/commentVotes.ts @@ -1,11 +1,13 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, - primaryKey, timestamp, + uniqueIndex, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { uuidv7 } from "uuidv7"; import { commmentVoteTypeEnum } from "~/src/drizzle/enums/commentVoteType"; import { commentsTable } from "./comments"; import { usersTable } from "./users"; @@ -13,10 +15,18 @@ import { usersTable } from "./users"; export const commentVotesTable = pgTable( "comment_votes", { + /** + * Foreign key reference to the id of the comment which is voted. + */ commentId: uuid("comment_id") .notNull() - .references(() => commentsTable.id, {}), - + .references(() => commentsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the vote was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -24,60 +34,76 @@ export const commentVotesTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - + /** + * Foreign key reference to the id of the user who first created the vote. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Primary unique identifier of the vote. + */ + id: uuid("id").primaryKey().$default(uuidv7), + /** + * Type of the vote. + */ type: commmentVoteTypeEnum("type").notNull(), - + /** + * Date time at the time the vote was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the vote. + */ + updaterId: uuid("updated_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updated_id").references(() => usersTable.id), - - voterId: uuid("voter_id").references(() => usersTable.id), }, (self) => [ - primaryKey({ - columns: [self.commentId, self.voterId], - }), index().on(self.commentId), - index().on(self.createdAt), index().on(self.creatorId), index().on(self.type), - index().on(self.voterId), + uniqueIndex().on(self.commentId, self.creatorId), ], ); export const commentVotesTableRelations = relations( commentVotesTable, ({ one }) => ({ + /** + * Many to one relationship from `comment_votes` table to `comments` table. + */ comment: one(commentsTable, { fields: [commentVotesTable.commentId], references: [commentsTable.id], relationName: "comment_votes.comment_id:comments.id", }), - + /** + * Many to one relationship from `comment_votes` table to `users` table. + */ creator: one(usersTable, { fields: [commentVotesTable.creatorId], references: [usersTable.id], relationName: "comment_votes.creator_id:users.id", }), - + /** + * Many to one relationship from `comment_votes` table to `users` table. + */ updater: one(usersTable, { fields: [commentVotesTable.updaterId], references: [usersTable.id], relationName: "comment_votes.updater_id:users.id", }), - - voter: one(usersTable, { - fields: [commentVotesTable.voterId], - references: [usersTable.id], - relationName: "comment_votes.voter_id:users.id", - }), }), ); + +export const commentVotesTableInsertSchema = + createInsertSchema(commentVotesTable); diff --git a/src/drizzle/tables/comments.ts b/src/drizzle/tables/comments.ts index 22fbdd190e..f31112cad8 100755 --- a/src/drizzle/tables/comments.ts +++ b/src/drizzle/tables/comments.ts @@ -1,12 +1,6 @@ -import { relations } from "drizzle-orm"; -import { - type AnyPgColumn, - index, - pgTable, - text, - timestamp, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { uuidv7 } from "uuidv7"; import { commentVotesTable } from "./commentVotes"; import { postsTable } from "./posts"; @@ -15,10 +9,13 @@ import { usersTable } from "./users"; export const commentsTable = pgTable( "comments", { + /** + * Body of the comment. + */ body: text("body").notNull(), - - commenterId: uuid("commenter_id").references(() => usersTable.id, {}), - + /** + * Date time at the time the comment was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -26,44 +23,47 @@ export const commentsTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - - id: uuid("id").primaryKey().$default(uuidv7), - - parentCommentId: uuid("parent_comment_id").references( - (): AnyPgColumn => commentsTable.id, - {}, - ), - - pinnedAt: timestamp("pinned_at", { - mode: "date", - precision: 3, - withTimezone: true, + /** + * Foreign key reference to the id of the user who first created the comment. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - pinnerId: uuid("pinner_id").references(() => usersTable.id, {}), - + /** + * Primary unique identifier of the comment. + */ + id: uuid("id").primaryKey().$default(uuidv7), + /** + * Foreign key reference to the id of the post on which the comment is made. + */ postId: uuid("post_id") .notNull() - .references(() => postsTable.id, {}), - + .references(() => postsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the comment was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the comment. + */ + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updater_id").references(() => usersTable.id), }, (self) => [ - index().on(self.commenterId), index().on(self.createdAt), index().on(self.creatorId), - index().on(self.parentCommentId), - index().on(self.pinnedAt), index().on(self.postId), ], ); @@ -71,44 +71,31 @@ export const commentsTable = pgTable( export const commentsTableRelations = relations( commentsTable, ({ many, one }) => ({ - childCommentsWhereParentComment: many(commentsTable, { - relationName: "comments.id:comments.parent_comment_id", - }), - - commenter: one(usersTable, { - fields: [commentsTable.commenterId], - references: [usersTable.id], - relationName: "comments.commenter_id:users.id", - }), - + /** + * One to many relationship from `comments` table to `comment_votes` table. + */ commentVotesWhereComment: many(commentVotesTable, { relationName: "comment_votes.comment_id:comments.id", }), - + /** + * Many to one relationship from `comments` table to `users` table. + */ creator: one(usersTable, { fields: [commentsTable.creatorId], references: [usersTable.id], relationName: "comments.creator_id:users.id", }), - - parentComment: one(commentsTable, { - fields: [commentsTable.parentCommentId], - references: [commentsTable.id], - relationName: "comments.id:comments.parent_comment_id", - }), - - pinner: one(usersTable, { - fields: [commentsTable.pinnerId], - references: [usersTable.id], - relationName: "comments.pinner_id:users.id", - }), - + /** + * Many to one relationship from `comments` table to `posts` table. + */ post: one(postsTable, { fields: [commentsTable.postId], references: [postsTable.id], relationName: "comments.post_id:posts.id", }), - + /** + * Many to one relationship from `comments` table to `users` table. + */ updater: one(usersTable, { fields: [commentsTable.updaterId], references: [usersTable.id], @@ -116,3 +103,7 @@ export const commentsTableRelations = relations( }), }), ); + +export const commentsTableInsertSchema = createInsertSchema(commentsTable, { + body: (schema) => schema.body.min(1).max(2048), +}); diff --git a/src/drizzle/tables/eventAttachments.ts b/src/drizzle/tables/eventAttachments.ts index 0595ec8e83..3763c4f99d 100755 --- a/src/drizzle/tables/eventAttachments.ts +++ b/src/drizzle/tables/eventAttachments.ts @@ -1,13 +1,5 @@ -import { relations } from "drizzle-orm"; -import { - index, - integer, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { eventAttachmentTypeEnum } from "~/src/drizzle/enums/eventAttachmentType"; import { eventsTable } from "./events"; import { usersTable } from "./users"; @@ -31,13 +23,13 @@ export const eventAttachmentsTable = pgTable( .notNull() .references(() => eventsTable.id), - position: integer("position").notNull(), - updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), @@ -49,7 +41,6 @@ export const eventAttachmentsTable = pgTable( index().on(self.eventId), index().on(self.createdAt), index().on(self.creatorId), - uniqueIndex().on(self.eventId, self.position), ], ); diff --git a/src/drizzle/tables/eventAttendances.ts b/src/drizzle/tables/eventAttendances.ts index 2743a07ce2..263c4baee6 100755 --- a/src/drizzle/tables/eventAttendances.ts +++ b/src/drizzle/tables/eventAttendances.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, timestamp, uuid } from "drizzle-orm/pg-core"; import { eventAttendeeRegistrationInviteStatusEnum } from "~/src/drizzle/enums/eventAttendeeRegistrationInviteStatus"; import { eventsTable } from "./events"; @@ -44,7 +44,9 @@ export const eventAttendancesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id), }, diff --git a/src/drizzle/tables/events.ts b/src/drizzle/tables/events.ts index ae1a981572..86a1f66f06 100755 --- a/src/drizzle/tables/events.ts +++ b/src/drizzle/tables/events.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import type { AnyPgColumn } from "drizzle-orm/pg-core"; import { boolean, @@ -77,7 +77,9 @@ export const eventsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/families.ts b/src/drizzle/tables/families.ts index c390823451..9e5019576f 100755 --- a/src/drizzle/tables/families.ts +++ b/src/drizzle/tables/families.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -39,7 +39,9 @@ export const familiesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/familyMemberships.ts b/src/drizzle/tables/familyMemberships.ts index 93ad7fd067..27af7e9135 100755 --- a/src/drizzle/tables/familyMemberships.ts +++ b/src/drizzle/tables/familyMemberships.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -37,7 +37,9 @@ export const familyMembershipsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/fundraisingCampaigns.ts b/src/drizzle/tables/fundraisingCampaigns.ts index 932188bc53..77d97ee67c 100755 --- a/src/drizzle/tables/fundraisingCampaigns.ts +++ b/src/drizzle/tables/fundraisingCampaigns.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, integer, @@ -54,7 +54,9 @@ export const fundraisingCampaignsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/funds.ts b/src/drizzle/tables/funds.ts index 013acdf772..5a7de8d9a3 100755 --- a/src/drizzle/tables/funds.ts +++ b/src/drizzle/tables/funds.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { boolean, index, @@ -46,7 +46,9 @@ export const fundsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/organizationMemberships.ts b/src/drizzle/tables/organizationMemberships.ts index e2f3525c23..fe3d671bdd 100755 --- a/src/drizzle/tables/organizationMemberships.ts +++ b/src/drizzle/tables/organizationMemberships.ts @@ -1,12 +1,12 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { - boolean, index, pgTable, primaryKey, timestamp, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { organizationMembershipRoleEnum } from "~/src/drizzle/enums/organizationMembershipRole"; import { organizationsTable } from "./organizations"; import { usersTable } from "./users"; @@ -15,7 +15,7 @@ export const organizationMembershipsTable = pgTable( "organization_memberships", { /** - * Datetime at the time the organization membership was created. + * Date time at the time the organization membership was created. */ createdAt: timestamp("created_at", { mode: "date", @@ -27,51 +27,59 @@ export const organizationMembershipsTable = pgTable( /** * Foreign key reference to the id of the user who first created the organization membership. */ - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - /** - * Boolean to tell whether the membership has been approved. - */ - isApproved: boolean("is_approved").notNull(), + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), /** * Foreign key reference to the id of the user the membership is associated to. */ memberId: uuid("member_id") .notNull() - .references(() => usersTable.id, {}), + .references(() => usersTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), /** * Foreign key reference to the id of the organization the membership is associated to. */ organizationId: uuid("organization_id") .notNull() - .references(() => organizationsTable.id, {}), + .references(() => organizationsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), /** * Role assigned to the user within the organization. */ role: organizationMembershipRoleEnum("role").notNull(), /** - * Datetime at the time the organization membership was last updated. + * Date time at the time the organization membership was last updated. */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), /** * Foreign key reference to the id of the user who last updated the organization membership. */ - updaterId: uuid("updater_id").references(() => usersTable.id), + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), }, (self) => [ - primaryKey({ - columns: [self.memberId, self.organizationId], - }), index().on(self.createdAt), index().on(self.creatorId), - index().on(self.isApproved), index().on(self.memberId), index().on(self.organizationId), + index().on(self.role), + primaryKey({ + columns: [self.memberId, self.organizationId], + }), ], ); @@ -103,3 +111,7 @@ export const organizationMembershipsTableRelations = relations( }), }), ); + +export const organizationMembershipsTableInsertSchema = createInsertSchema( + organizationMembershipsTable, +); diff --git a/src/drizzle/tables/organizations.ts b/src/drizzle/tables/organizations.ts index 72b53eacd8..06bd9d091c 100755 --- a/src/drizzle/tables/organizations.ts +++ b/src/drizzle/tables/organizations.ts @@ -1,12 +1,6 @@ -import { relations } from "drizzle-orm"; -import { - boolean, - index, - pgTable, - text, - timestamp, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { uuidv7 } from "uuidv7"; import { iso3166Alpha2CountryCodeEnum } from "~/src/drizzle/enums/iso3166Alpha2CountryCode"; import { actionCategoriesTable } from "./actionCategories"; @@ -16,11 +10,13 @@ import { familiesTable } from "./families"; import { fundsTable } from "./funds"; import { organizationMembershipsTable } from "./organizationMemberships"; import { postsTable } from "./posts"; -import { tagFoldersTable } from "./tagFolders"; import { tagsTable } from "./tags"; import { usersTable } from "./users"; import { venuesTable } from "./venues"; +/** + * Drizzle orm postgres table definition for organizations. + */ export const organizationsTable = pgTable( "organizations", { @@ -41,7 +37,7 @@ export const organizationsTable = pgTable( */ countryCode: iso3166Alpha2CountryCodeEnum("country_code"), /** - * Datetime at the time the organization was created. + * Date time at the time the organization was created. */ createdAt: timestamp("created_at", { mode: "date", @@ -53,9 +49,10 @@ export const organizationsTable = pgTable( /** * Foreign key reference to the id of the user who first created the user. */ - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), /** * Custom information about the organization. */ @@ -64,10 +61,6 @@ export const organizationsTable = pgTable( * Primary unique identifier of the organization. */ id: uuid("id").primaryKey().$default(uuidv7), - /** - * Boolean field to tell whether the organization requires manual verification for membership. - */ - isPrivate: boolean("is_private").notNull(), /** * Name of the organization. */ @@ -81,17 +74,22 @@ export const organizationsTable = pgTable( */ state: text("state"), /** - * Datetime at the time the organization was last updated. + * Date time at the time the organization was last updated. */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), /** * Foreign key reference to the id of the user who last updated the organization. */ - updaterId: uuid("updater_id").references(() => usersTable.id), + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), }, (self) => [ index().on(self.creatorId), @@ -103,31 +101,47 @@ export const organizationsTable = pgTable( export const organizationsTableRelations = relations( organizationsTable, ({ one, many }) => ({ + /** + * One to many relationship from `organizations` table to `actions` table. + */ actionsWhereOrganization: many(actionsTable, { relationName: "actions.organization_id:organizations.id", }), + /** + * One to many relationship from `organizations` table to `action_categories` table. + */ actionCategoriesWhereOrganization: many(actionCategoriesTable, { relationName: "action_categories.organization_id:organizations.id", }), - + /** + * One to many relationship from `organizations` table to `advertisements` table. + */ advertisementsWhereOrganization: many(advertisementsTable, { relationName: "advertisements.organization_id:organizations.id", }), - + /** + * Many to one relationship from `organizations` table to `users` table. + */ creator: one(usersTable, { fields: [organizationsTable.creatorId], references: [usersTable.id], relationName: "organizations.creator_id:users.id", }), - + /** + * One to many relationship from `organizations` table to `families` table. + */ familiesWhereOrganization: many(familiesTable, { relationName: "families.organization_id:organizations.id", }), - + /** + * One to many relationship from `organizations` table to `funds` table. + */ fundsWhereOrganization: many(fundsTable, { relationName: "funds.organization_id:organizations.id", }), - + /** + * One to many relationship from `organizations` table to `organization_memberships` table. + */ organizationMembershipsWhereOrganization: many( organizationMembershipsTable, { @@ -135,27 +149,44 @@ export const organizationsTableRelations = relations( "organization_memberships.organization_id:organizations.id", }, ), - + /** + * One to many relationship from `organizations` table to `posts` table. + */ postsWhereOrganization: many(postsTable, { relationName: "organizations.id:posts.organization_id", }), - - tagFoldersWhereOrganization: many(tagFoldersTable, { - relationName: "organizations.id:tag_folders.organization_id", - }), - + /** + * One to many relationship from `organizations` table to `tags` table. + */ tagsWhereOrganization: many(tagsTable, { relationName: "organizations.id:tags.organization_id", }), - + /** + * Many to one relationship from `organizations` table to `users` table. + */ updater: one(usersTable, { fields: [organizationsTable.updaterId], references: [usersTable.id], relationName: "organizations.updater_id:users.id", }), - + /** + * One to many relationship from `organizations` table to `venues` table. + */ venuesWhereOrganization: many(venuesTable, { relationName: "organizations.id:venues.organization_id", }), }), ); + +export const organizationsTableInsertSchema = createInsertSchema( + organizationsTable, + { + address: (schema) => schema.address.min(1).max(1024), + avatarURI: (schema) => schema.avatarURI.min(1), + city: (schema) => schema.city.min(1).max(64), + description: (schema) => schema.description.min(1).max(2048), + name: (schema) => schema.name.min(1).max(256), + postalCode: (schema) => schema.postalCode.min(1).max(32), + state: (schema) => schema.state.min(1).max(64), + }, +); diff --git a/src/drizzle/tables/pledges.ts b/src/drizzle/tables/pledges.ts index 9bc1004f7d..eaad00b8a0 100755 --- a/src/drizzle/tables/pledges.ts +++ b/src/drizzle/tables/pledges.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { boolean, index, @@ -57,7 +57,9 @@ export const pledgesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/postAttachments.ts b/src/drizzle/tables/postAttachments.ts index 0f6e5c4eeb..f23cd07815 100755 --- a/src/drizzle/tables/postAttachments.ts +++ b/src/drizzle/tables/postAttachments.ts @@ -1,13 +1,6 @@ -import { relations } from "drizzle-orm"; -import { - index, - integer, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { postAttachmentTypeEnum } from "~/src/drizzle/enums/postAttachmentType"; import { postsTable } from "./posts"; import { usersTable } from "./users"; @@ -15,6 +8,9 @@ import { usersTable } from "./users"; export const postAttachmentsTable = pgTable( "post_attachments", { + /** + * Date time at the time the attachment was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -22,52 +18,70 @@ export const postAttachmentsTable = pgTable( }) .notNull() .defaultNow(), - + /** + * Foreign key reference to the id of the user who first created the attachment. + */ creatorId: uuid("creator_id") .references(() => usersTable.id, {}) .notNull(), - - position: integer("position").notNull(), - + /** + * Foreign key reference to the id of the post that the attachment is associated to. + */ postId: uuid("post_id") .notNull() .references(() => postsTable.id), - + /** + * Type of the attachment. + */ type: postAttachmentTypeEnum("type").notNull(), - + /** + * Foreign key reference to the id of the user who last updated the attachment. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), - + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the attachment. + */ updaterId: uuid("updater_id").references(() => usersTable.id, {}), - + /** + * URI to the attachment. + */ uri: text("uri", {}).notNull(), }, (self) => [ index().on(self.createdAt), index().on(self.creatorId), index().on(self.postId), - uniqueIndex().on(self.position, self.postId), ], ); export const postAttachmentsTableRelations = relations( postAttachmentsTable, ({ one }) => ({ + /** + * Many to one relationship from `post_attachments` table to `users` table. + */ creator: one(usersTable, { fields: [postAttachmentsTable.creatorId], references: [usersTable.id], relationName: "post_attachments.creator_id:users.id", }), - + /** + * Many to one relationship from `post_attachments` table to `posts` table. + */ post: one(postsTable, { fields: [postAttachmentsTable.postId], references: [postsTable.id], relationName: "post_attachments.post_id:posts.id", }), - + /** + * Many to one relationship from `post_attachments` table to `users` table. + */ updater: one(usersTable, { fields: [postAttachmentsTable.updaterId], references: [usersTable.id], @@ -75,3 +89,10 @@ export const postAttachmentsTableRelations = relations( }), }), ); + +export const postAttachmentsTableInsertSchema = createInsertSchema( + postAttachmentsTable, + { + uri: (schema) => schema.uri.min(1), + }, +); diff --git a/src/drizzle/tables/postVotes.ts b/src/drizzle/tables/postVotes.ts index 0e67fdc074..a15b628fd0 100755 --- a/src/drizzle/tables/postVotes.ts +++ b/src/drizzle/tables/postVotes.ts @@ -1,11 +1,13 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, - primaryKey, timestamp, + uniqueIndex, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { uuidv7 } from "uuidv7"; import { postVoteTypeEnum } from "~/src/drizzle/enums/postVoteType"; import { postsTable } from "./posts"; import { usersTable } from "./users"; @@ -13,6 +15,9 @@ import { usersTable } from "./users"; export const postVotesTable = pgTable( "post_votes", { + /** + * Date time at the time the vote was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -20,61 +25,81 @@ export const postVotesTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - + /** + * Foreign key reference to the id of the user who first created the vote. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Primary unique identifier of the vote. + */ + id: uuid("id").primaryKey().$default(uuidv7), + /** + * Foreign key reference to the id of the post which is voted. + */ postId: uuid("post_id") .notNull() - .references(() => postsTable.id, {}), - + .references(() => postsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Type of the vote. + */ + type: postVoteTypeEnum("type").notNull(), + /** + * Date time at the time the vote was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the vote. + */ + updaterId: uuid("updated_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updated_id").references(() => usersTable.id), - - type: postVoteTypeEnum("type").notNull(), - - voterId: uuid("voter_id").references(() => usersTable.id), }, (self) => [ - primaryKey({ - columns: [self.postId, self.voterId], - }), - index().on(self.createdAt), index().on(self.creatorId), index().on(self.postId), index().on(self.type), - index().on(self.voterId), + uniqueIndex().on(self.creatorId, self.postId), ], ); export const postVotesTableRelations = relations(postVotesTable, ({ one }) => ({ + /** + * Many to one relationship from `post_votes` table to `users` table. + */ creator: one(usersTable, { fields: [postVotesTable.creatorId], references: [usersTable.id], relationName: "post_votes.creator_id:users.id", }), - + /** + * Many to one relationship from `post_votes` table to `posts` table. + */ post: one(postsTable, { fields: [postVotesTable.postId], references: [postsTable.id], relationName: "post_votes.post_id:posts.id", }), - + /** + * Many to one relationship from `post_votes` table to `users` table. + */ updater: one(usersTable, { fields: [postVotesTable.updaterId], references: [usersTable.id], relationName: "post_votes.updater_id:users.id", }), - - voter: one(usersTable, { - fields: [postVotesTable.voterId], - references: [usersTable.id], - relationName: "post_votes.voter_id:users.id", - }), })); + +export const postVotesTableInsertSchema = createInsertSchema(postVotesTable); diff --git a/src/drizzle/tables/posts.ts b/src/drizzle/tables/posts.ts index 21ea21e024..ad30869cbb 100755 --- a/src/drizzle/tables/posts.ts +++ b/src/drizzle/tables/posts.ts @@ -1,5 +1,6 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { uuidv7 } from "uuidv7"; import { commentsTable } from "./comments"; import { organizationsTable } from "./organizations"; @@ -10,8 +11,13 @@ import { usersTable } from "./users"; export const postsTable = pgTable( "posts", { + /** + * Caption of the post. + */ caption: text("caption").notNull(), - + /** + * Date time at the time the post was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -19,84 +25,107 @@ export const postsTable = pgTable( }) .notNull() .defaultNow(), - + /** + * Foreign key reference to the id of the user who first created the post. + */ creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - + .notNull() + .references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Primary unique identifier of the post. + */ id: uuid("id").primaryKey().$default(uuidv7), - + /** + * Foreign key reference to the id of the organization in which the post is made. + */ organizationId: uuid("organization_id") .notNull() - .references(() => organizationsTable.id, {}), - + .references(() => organizationsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the post was pinned. + */ pinnedAt: timestamp("pinned_at", { mode: "date", precision: 3, withTimezone: true, }), - - pinnerId: uuid("pinner_id").references(() => usersTable.id, {}), - - posterId: uuid("poster_id").references(() => usersTable.id, {}), - + /** + * Date time at the time the post was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the post. + */ + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updater_id").references(() => usersTable.id), }, (self) => [ index().on(self.createdAt), index().on(self.creatorId), - index().on(self.organizationId), index().on(self.pinnedAt), - index().on(self.posterId), + index().on(self.organizationId), ], ); export const postsTableRelations = relations(postsTable, ({ many, one }) => ({ + /** + * One to many relationship from `posts` table to `comments` table. + */ commentsWherePost: many(commentsTable, { relationName: "comments.post_id:posts.id", }), - + /** + * Many to one relationship from `posts` table to `users` table. + */ creator: one(usersTable, { fields: [postsTable.creatorId], references: [usersTable.id], relationName: "posts.creator_id:users.id", }), - + /** + * Many to one relationship from `posts` table to `organizations` table. + */ organization: one(organizationsTable, { fields: [postsTable.organizationId], references: [organizationsTable.id], relationName: "organizations.id:posts.organization_id", }), - - pinner: one(usersTable, { - fields: [postsTable.pinnerId], - references: [usersTable.id], - relationName: "posts.pinner_id:users.id", - }), - - poster: one(usersTable, { - fields: [postsTable.posterId], - references: [usersTable.id], - relationName: "posts.poster_id:users.id", - }), - + /** + * One to many relationship from `posts` table to `post_attachments` table. + */ postAttachmentsWherePost: many(postAttachmentsTable, { relationName: "post_attachments.post_id:posts.id", }), - + /** + * One to many relationship from `posts` table to `post_votes` table. + */ postVotesWherePost: many(postVotesTable, { relationName: "post_votes.post_id:posts.id", }), - + /** + * Many to one relationship from `posts` table to `users` table. + */ updater: one(usersTable, { fields: [postsTable.updaterId], references: [usersTable.id], relationName: "posts.updater_id:users.id", }), })); + +export const postsTableInsertSchema = createInsertSchema(postsTable, { + caption: (schema) => schema.caption.min(1).max(2048), +}); diff --git a/src/drizzle/tables/recurrences.ts b/src/drizzle/tables/recurrences.ts index 32b0142950..97291209da 100755 --- a/src/drizzle/tables/recurrences.ts +++ b/src/drizzle/tables/recurrences.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, integer, @@ -51,7 +51,9 @@ export const recurrencesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), diff --git a/src/drizzle/tables/tagAssignments.ts b/src/drizzle/tables/tagAssignments.ts index 5426896bea..4289ce2c69 100755 --- a/src/drizzle/tables/tagAssignments.ts +++ b/src/drizzle/tables/tagAssignments.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -6,16 +6,28 @@ import { timestamp, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { tagsTable } from "./tags"; import { usersTable } from "./users"; +/** + * Drizzle orm postgres table definition for tag assignments. + */ export const tagAssignmentsTable = pgTable( "tag_assignments", { + /** + * Foreign key reference to the id of the user who has been assigned. + */ assigneeId: uuid("assignee_id") .notNull() - .references(() => usersTable.id, {}), - + .references(() => usersTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the tag assignment was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -23,53 +35,81 @@ export const tagAssignmentsTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - - tagId: uuid("tag_id").references(() => tagsTable.id), - + /** + * Foreign key reference to the id of the user who first created the tag assignment. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Foreign key reference to the id of the tag that is assigned. + */ + tagId: uuid("tag_id") + .notNull() + .references(() => tagsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Date time at the time the tag assignment was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the tag assignment. + */ + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updater_id").references(() => usersTable.id), }, (self) => [ - primaryKey({ - columns: [self.assigneeId, self.tagId], - }), index().on(self.assigneeId), index().on(self.createdAt), index().on(self.creatorId), index().on(self.tagId), + primaryKey({ + columns: [self.assigneeId, self.tagId], + }), ], ); export const tagAssignmentsTableRelations = relations( tagAssignmentsTable, ({ one }) => ({ + /** + * Many to one relationship from `tag_assignments` table to `users` table. + */ assignee: one(usersTable, { fields: [tagAssignmentsTable.assigneeId], references: [usersTable.id], relationName: "tag_assignments.assignee_id:users.id", }), - + /** + * Many to one relationship from `tag_assignments` table to `users` table. + */ creator: one(usersTable, { fields: [tagAssignmentsTable.creatorId], references: [usersTable.id], relationName: "tag_assignments.creator_id:users.id", }), - + /** + * Many to one relationship from `tag_assignments` table to `tags` table. + */ tag: one(tagsTable, { fields: [tagAssignmentsTable.tagId], references: [tagsTable.id], relationName: "tag_assignments.tag_id:tags.id", }), - + /** + * Many to one relationship from `tag_assignments` table to `users` table. + */ updater: one(usersTable, { fields: [tagAssignmentsTable.updaterId], references: [usersTable.id], @@ -77,3 +117,6 @@ export const tagAssignmentsTableRelations = relations( }), }), ); + +export const tagAssignmentsTableInsertSchema = + createInsertSchema(tagAssignmentsTable); diff --git a/src/drizzle/tables/tagFolders.ts b/src/drizzle/tables/tagFolders.ts deleted file mode 100755 index 9dd856a2c3..0000000000 --- a/src/drizzle/tables/tagFolders.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { relations } from "drizzle-orm"; -import type { AnyPgColumn } from "drizzle-orm/pg-core"; -import { - index, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; -import { uuidv7 } from "uuidv7"; -import { organizationsTable } from "./organizations"; -import { tagsTable } from "./tags"; -import { usersTable } from "./users"; - -export const tagFoldersTable = pgTable( - "tag_folders", - { - createdAt: timestamp("created_at", { - mode: "date", - precision: 3, - withTimezone: true, - }) - .notNull() - .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - - id: uuid("id").primaryKey().$default(uuidv7), - - name: text("name").notNull(), - - organizationId: uuid("organization_id") - .notNull() - .references(() => organizationsTable.id, {}), - - parentFolderId: uuid("parent_folder_id").references( - (): AnyPgColumn => tagFoldersTable.id, - ), - - updatedAt: timestamp("updated_at", { - mode: "date", - precision: 3, - withTimezone: true, - }), - - updaterId: uuid("updater_id").references(() => usersTable.id), - }, - (self) => [ - index().on(self.createdAt), - index().on(self.creatorId), - index().on(self.name), - index().on(self.organizationId), - uniqueIndex().on(self.name, self.organizationId), - ], -); - -export const tagFoldersTableRelations = relations( - tagFoldersTable, - ({ many, one }) => ({ - creator: one(usersTable, { - fields: [tagFoldersTable.creatorId], - references: [usersTable.id], - relationName: "tag_folders.creator_id:users.id", - }), - - organization: one(organizationsTable, { - fields: [tagFoldersTable.organizationId], - references: [organizationsTable.id], - relationName: "organizations.id:tag_folders.organization_id", - }), - - parentFolder: one(tagFoldersTable, { - fields: [tagFoldersTable.parentFolderId], - references: [tagFoldersTable.id], - relationName: "tag_folders.id:tag_folders.parent_folder_id", - }), - - tagFoldersWhereParentFolder: many(tagFoldersTable, { - relationName: "tag_folders.id:tag_folders.parent_folder_id", - }), - - tagsWhereFolder: many(tagsTable, { - relationName: "tag_folders.id:tags.folder_id", - }), - - updater: one(usersTable, { - fields: [tagFoldersTable.updaterId], - references: [usersTable.id], - relationName: "tag_folders.updater_id:users.id", - }), - }), -); diff --git a/src/drizzle/tables/tags.ts b/src/drizzle/tables/tags.ts index 0d9f087012..bc2e5ed1cc 100755 --- a/src/drizzle/tables/tags.ts +++ b/src/drizzle/tables/tags.ts @@ -1,5 +1,7 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { + type AnyPgColumn, + boolean, index, pgTable, text, @@ -7,15 +9,21 @@ import { uniqueIndex, uuid, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { uuidv7 } from "uuidv7"; import { organizationsTable } from "./organizations"; import { tagAssignmentsTable } from "./tagAssignments"; -import { tagFoldersTable } from "./tagFolders"; import { usersTable } from "./users"; +/** + * Drizzle orm postgres table definition for tags. + */ export const tagsTable = pgTable( "tags", { + /** + * Date time at the time the tag was created. + */ createdAt: timestamp("created_at", { mode: "date", precision: 3, @@ -23,65 +31,119 @@ export const tagsTable = pgTable( }) .notNull() .defaultNow(), - - creatorId: uuid("creator_id") - .references(() => usersTable.id, {}) - .notNull(), - - folderId: uuid("folder_id").references(() => tagFoldersTable.id), - + /** + * Foreign key reference to the id of the user who first created the tag. + */ + creatorId: uuid("creator_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), + /** + * Primary unique identifier of the tag. + */ id: uuid("id").primaryKey().$default(uuidv7), - + /** + * Boolean to tell if the tag is to be used as a tag folder. + */ + isFolder: boolean("is_folder").notNull(), + /** + * Name of the state the tag. + */ name: text("name").notNull(), - + /** + * Foreign key reference to the id of the organization within which the tag is created. + */ organizationId: uuid("organization_id") .notNull() - .references(() => organizationsTable.id, {}), - + .references(() => organizationsTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + /** + * Foreign key reference to the id of the parent tag. + */ + parentTagId: uuid("parent_tag_id").references( + (): AnyPgColumn => tagsTable.id, + { + onDelete: "cascade", + onUpdate: "cascade", + }, + ), + /** + * Date time at the time the tag was last updated. + */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), + /** + * Foreign key reference to the id of the user who last updated the tag. + */ + updaterId: uuid("updater_id").references(() => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", }), - - updaterId: uuid("updater_id").references(() => usersTable.id), }, (self) => [ - index().on(self.createdAt), index().on(self.creatorId), - index().on(self.folderId), + index().on(self.isFolder), index().on(self.name), index().on(self.organizationId), - uniqueIndex().on(self.name, self.organizationId), + index().on(self.parentTagId), + uniqueIndex().on(self.isFolder, self.name, self.organizationId), ], ); export const tagsTableRelations = relations(tagsTable, ({ many, one }) => ({ + /** + * Many to one relationship from `tags` table to `users` table. + */ creator: one(usersTable, { fields: [tagsTable.creatorId], references: [usersTable.id], relationName: "tags.creator_id:users.id", }), - - folder: one(tagFoldersTable, { - fields: [tagsTable.folderId], - references: [tagFoldersTable.id], - relationName: "tag_folders.id:tags.folder_id", - }), - + /** + * Many to one relationship from `tags` table to `organizations` table. + */ organization: one(organizationsTable, { fields: [tagsTable.organizationId], references: [organizationsTable.id], relationName: "organizations.id:tags.organization_id", }), - + /** + * Many to one relationship from `tags` table to `tags` table. + */ + parentTag: one(tagsTable, { + fields: [tagsTable.parentTagId], + references: [tagsTable.id], + relationName: "tags.id:tags.parent_tag_id", + }), + /** + * One to many relationship from `tags` table to `tag_assignments` table. + */ tagAssignmentsWhereTag: many(tagAssignmentsTable, { relationName: "tag_assignments.tag_id:tags.id", }), - + /** + * One to many relationship from `tags` table to `tags` table. + */ + tagsWhereParentTag: many(tagsTable, { + relationName: "tags.id:tags.parent_tag_id", + }), + /** + * Many to one relationship from `tags` table to `users` table. + */ updater: one(usersTable, { fields: [tagsTable.updaterId], references: [usersTable.id], relationName: "tags.updater_id:users.id", }), })); + +export const tagsTableInsertSchema = createInsertSchema(tagsTable, { + name: (schema) => schema.name.min(1).max(256), +}); diff --git a/src/drizzle/tables/users.ts b/src/drizzle/tables/users.ts index 76e34ffdf9..5471e3fd4e 100755 --- a/src/drizzle/tables/users.ts +++ b/src/drizzle/tables/users.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import type { AnyPgColumn } from "drizzle-orm/pg-core"; import { boolean, @@ -37,7 +37,6 @@ import { postAttachmentsTable } from "./postAttachments"; import { postVotesTable } from "./postVotes"; import { postsTable } from "./posts"; import { tagAssignmentsTable } from "./tagAssignments"; -import { tagFoldersTable } from "./tagFolders"; import { tagsTable } from "./tags"; import { venueAttachmentsTable } from "./venueAttachments"; import { venueBookingsTable } from "./venueBookings"; @@ -74,7 +73,7 @@ export const usersTable = pgTable( */ countryCode: iso3166Alpha2CountryCodeEnum("country_code"), /** - * Datetime at the time the user was created. + * Date time at the time the user was created. */ createdAt: timestamp("created_at", { mode: "date", @@ -86,9 +85,10 @@ export const usersTable = pgTable( /** * Foreign key reference to the id of the user who first created the user. */ - creatorId: uuid("creator_id") - .references((): AnyPgColumn => usersTable.id) - .notNull(), + creatorId: uuid("creator_id").references((): AnyPgColumn => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), /** * Custom information about the user. */ @@ -150,17 +150,22 @@ export const usersTable = pgTable( */ state: text("state"), /** - * Datetime at the time the user was last updated. + * Date time at the time the user was last updated. */ updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }).$onUpdate(() => new Date()), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), /** * Foreign key reference to the id of the user who last updated the user. */ - updaterId: uuid("updater_id").references((): AnyPgColumn => usersTable.id), + updaterId: uuid("updater_id").references((): AnyPgColumn => usersTable.id, { + onDelete: "set null", + onUpdate: "cascade", + }), /** * The phone number to use to communicate with the user while they're at work. */ @@ -173,282 +178,382 @@ export const usersTable = pgTable( ], ); -export const usersTableRelations = relations(usersTable, ({ many }) => ({ +export const usersTableRelations = relations(usersTable, ({ many, one }) => ({ + /** + * One to many relationship from `users` table to `actions` table. + */ actionsWhereAssignee: many(actionsTable, { relationName: "actions.assignee_id:users.id", }), - + /** + * One to many relationship from `users` table to `actions` table. + */ actionsWhereCreator: many(actionsTable, { relationName: "actions.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `actions` table. + */ actionsWhereUpdater: many(actionsTable, { relationName: "actions.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `action_categories` table. + */ actionCategoriesWhereCreator: many(actionCategoriesTable, { relationName: "action_categories.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `action_categories` table. + */ actionCategoriesWhereUpdater: many(actionCategoriesTable, { relationName: "action_categories.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `advertisement_attachments` table. + */ advertisementAttachmentsWhereCreator: many(advertisementAttachmentsTable, { relationName: "advertisement_attachments.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `advertisement_attachments` table. + */ advertisementAttachmentsWhereUpdater: many(advertisementAttachmentsTable, { relationName: "advertisement_attachments.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `advertisements` table. + */ advertisementsWhereCreator: many(advertisementsTable, { relationName: "advertisements.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `advertisements` table. + */ advertisementsWhereUpdater: many(advertisementsTable, { relationName: "advertisements.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `agenda_sections` table. + */ agendaSectionsWhereCreator: many(agendaSectionsTable, { relationName: "agenda_sections.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `agenda_sections` table. + */ agendaSectionsWhereUpdater: many(agendaSectionsTable, { relationName: "agenda_sections.updater_id:users.id", }), - - commentsWhereCommenter: many(commentsTable, { - relationName: "comments.commenter_id:users.id", - }), - + /** + * One to many relationship from `users` table to `comments` table. + */ commentsWhereCreator: many(commentsTable, { relationName: "comments.creator_id:users.id", }), - - commentsWherePinner: many(commentsTable, { - relationName: "comments.pinner_id:users.id", - }), - + /** + * One to many relationship from `users` table to `comments` table. + */ commentsWhereUpdater: many(commentsTable, { relationName: "comments.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `comment_votes` table. + */ commentVotesWhereCreator: many(commentVotesTable, { relationName: "comment_votes.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `comment_votes` table. + */ commentVotesWhereUpdater: many(commentVotesTable, { relationName: "comment_votes.updater_id:users.id", }), - - commentVotesWhereVoter: many(commentVotesTable, { - relationName: "comment_votes.voter_id:users.id", - }), - + /** + * Many to one relationship from `users` table to `users` table. + */ + creator: one(usersTable, { + fields: [usersTable.creatorId], + references: [usersTable.id], + relationName: "users.creator_id:users.id", + }), + /** + * One to many relationship from `users` table to `events` table. + */ eventsWhereCreator: many(eventsTable, { relationName: "events.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `events` table. + */ eventsWhereUpdater: many(eventsTable, { relationName: "events.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `event_attachments` table. + */ eventAttachmentsWhereCreator: many(eventAttachmentsTable, { relationName: "event_attachments.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `event_attachments` table. + */ eventAttachmentsWhereUpdater: many(eventAttachmentsTable, { relationName: "event_attachments.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `families` table. + */ familiesWhereCreator: many(familiesTable, { relationName: "families.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `families` table. + */ familiesWhereUpdater: many(familiesTable, { relationName: "families.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `family_memberships` table. + */ familyMembershipsWhereCreator: many(familyMembershipsTable, { relationName: "family_memberships.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `family_memberships` table. + */ familyMembershipsWhereMember: many(familyMembershipsTable, { relationName: "family_memberships.member_id:users.id", }), - + /** + * One to many relationship from `users` table to `family_memberships` table. + */ familyMembershipsWhereUpdater: many(familyMembershipsTable, { relationName: "family_memberships.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `fundraising_campaigns` table. + */ fundraisingCampaignsWhereCreator: many(fundraisingCampaignsTable, { relationName: "fundraising_campaigns.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `fundraising_campaigns` table. + */ fundraisingCampaignsWhereUpdater: many(fundraisingCampaignsTable, { relationName: "fundraising_campaigns.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `funds` table. + */ fundsWhereCreator: many(fundsTable, { relationName: "funds.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `funds` table. + */ fundsWhereUpdater: many(fundsTable, { relationName: "funds.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `organizations` table. + */ organizationsWhereCreator: many(organizationsTable, { relationName: "organizations.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `organizations` table. + */ organizationsWhereUpdater: many(organizationsTable, { relationName: "organizations.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `organization_memberships` table. + */ organizationMembershipsWhereCreator: many(organizationMembershipsTable, { relationName: "organization_memberships.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `organization_memberships` table. + */ organizationMembershipsWhereMember: many(organizationMembershipsTable, { relationName: "organization_memberships.member_id:users.id", }), - - organizationMembershipsWhereUpdator: many(organizationMembershipsTable, { + /** + * One to many relationship from `users` table to `organization_memberships` table. + */ + organizationMembershipsWhereUpdater: many(organizationMembershipsTable, { relationName: "organization_memberships.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `pledges` table. + */ pledgesWhereCreator: many(pledgesTable, { relationName: "pledges.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `pledges` table. + */ pledgesWherePledger: many(pledgesTable, { relationName: "pledges.pledger_id:users.id", }), - + /** + * One to many relationship from `users` table to `pledges` table. + */ pledgesWhereUpdater: many(pledgesTable, { relationName: "pledges.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `posts` table. + */ postsWhereCreator: many(postsTable, { relationName: "posts.creator_id:users.id", }), - - postsWherePinner: many(postsTable, { - relationName: "posts.pinner_id:users.id", - }), - - postsWherePoster: many(postsTable, { - relationName: "posts.poster_id:users.id", - }), - + /** + * One to many relationship from `users` table to `posts` table. + */ postsWhereUpdater: many(postsTable, { relationName: "posts.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `post_attachments` table. + */ postAttachmentsWhereCreator: many(postAttachmentsTable, { relationName: "post_attachments.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `post_attachments` table. + */ postAttachmentsWhereUpdater: many(postAttachmentsTable, { relationName: "post_attachments.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `post_votes` table. + */ postVotesWhereCreator: many(postVotesTable, { relationName: "post_votes.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `post_votes` table. + */ postVotesWhereUpdater: many(postVotesTable, { relationName: "post_votes.updater_id:users.id", }), - - postVotesWhereVoter: many(postVotesTable, { - relationName: "post_votes.voter_id:users.id", - }), - + /** + * One to many relationship from `users` table to `tags` table. + */ tagsWhereCreator: many(tagsTable, { relationName: "tags.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `tags` table. + */ tagsWhereUpdater: many(tagsTable, { relationName: "tags.updater_id:users.id", }), - + /** + * One to many relationship from `users` table to `tag_assignments` table. + */ tagAssignmentsWhereAssignee: many(tagAssignmentsTable, { relationName: "tag_assignments.assignee_id:users.id", }), - + /** + * One to many relationship from `users` table to `tag_assignments` table. + */ tagAssignmentsWhereCreator: many(tagAssignmentsTable, { relationName: "tag_assignments.creator_id:users.id", }), - + /** + * One to many relationship from `users` table to `tag_assignments` table. + */ tagAssignmentsWhereUpdater: many(tagAssignmentsTable, { relationName: "tag_assignments.updater_id:users.id", }), - - tagFoldersWhereCreator: many(tagFoldersTable, { - relationName: "tag_folders.creator_id:users.id", - }), - - tagFoldersWhereUpdater: many(tagFoldersTable, { - relationName: "tag_folders.updater_id:users.id", - }), - + /** + * Many to one relationship from `users` table to `users` table. + */ + updater: one(usersTable, { + fields: [usersTable.updaterId], + references: [usersTable.id], + relationName: "users.id:users.updater_id", + }), + /** + * One to many relationship from `users` table to `venues` table. + */ venuesWhereCreator: many(venuesTable, { relationName: "users.id:venues.creator_id", }), - + /** + * One to many relationship from `users` table to `venues` table. + */ venuesWhereUpdater: many(venuesTable, { relationName: "users.id:venues.updater_id", }), - + /** + * One to many relationship from `users` table to `venue_attachments` table. + */ venueAttachmentsWhereCreator: many(venueAttachmentsTable, { relationName: "users.id:venue_attachments.creator_id", }), - + /** + * One to many relationship from `users` table to `venue_attachments` table. + */ venueAttachmentsWhereUpdater: many(venueAttachmentsTable, { relationName: "users.id:venue_attachments.updater_id", }), - + /** + * One to many relationship from `users` table to `venue_bookings` table. + */ venueBookingsWhereCreator: many(venueBookingsTable, { relationName: "users.id:venue_bookings.creator_id", }), - + /** + * One to many relationship from `users` table to `venue_bookings` table. + */ venueBookingsWhereUpdater: many(venueBookingsTable, { relationName: "users.id:venue_bookings.updater_id", }), - + /** + * One to many relationship from `users` table to `volunteer_groups` table. + */ volunteerGroupsWhereCreator: many(volunteerGroupsTable, { relationName: "users.id:volunteer_groups.creator_id", }), - + /** + * One to many relationship from `users` table to `volunteer_groups` table. + */ volunteerGroupsWhereLeader: many(volunteerGroupsTable, { relationName: "users.id:volunteer_groups.leader_id", }), - + /** + * One to many relationship from `users` table to `volunteer_groups` table. + */ volunteerGroupsWhereUpdater: many(volunteerGroupsTable, { relationName: "users.id:volunteer_groups.updater_id", }), - + /** + * One to many relationship from `users` table to `volunteer_group_assignments` table. + */ volunteerGroupAssignmentsWhereAssignee: many(volunteerGroupAssignmentsTable, { relationName: "users.id:volunteer_group_assignments.assignee_id", }), - + /** + * One to many relationship from `users` table to `volunteer_group_assignments` table. + */ volunteerGroupAssignmentsWhereCreator: many(volunteerGroupAssignmentsTable, { relationName: "users.id:volunteer_group_assignments.creator_id", }), - + /** + * One to many relationship from `users` table to `volunteer_group_assignments` table. + */ volunteerGroupAssignmentsWhereUpdater: many(volunteerGroupAssignmentsTable, { relationName: "users.id:volunteer_group_assignments.updater_id", }), })); -/** - * Zod schema for parsing the inserts to the users table. - */ export const usersTableInsertSchema = createInsertSchema(usersTable, { address: (schema) => schema.address.min(1).max(1024), - avatarURI: (schema) => schema.avatarURI.min(1).max(2048), + avatarURI: (schema) => schema.avatarURI.min(1), city: (schema) => schema.city.min(1).max(64), description: (schema) => schema.description.min(1).max(2048), name: (schema) => schema.name.min(1).max(256), diff --git a/src/drizzle/tables/venueAttachments.ts b/src/drizzle/tables/venueAttachments.ts index 258867cc39..01cd8698ed 100755 --- a/src/drizzle/tables/venueAttachments.ts +++ b/src/drizzle/tables/venueAttachments.ts @@ -1,13 +1,5 @@ -import { relations } from "drizzle-orm"; -import { - index, - integer, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { venueAttachmentTypeEnum } from "~/src/drizzle/enums/venueAttachmentType"; import { usersTable } from "./users"; import { venuesTable } from "./venues"; @@ -27,15 +19,15 @@ export const venueAttachmentsTable = pgTable( .references(() => usersTable.id, {}) .notNull(), - position: integer("position").notNull(), - type: venueAttachmentTypeEnum("type").notNull(), updatedAt: timestamp("updated_at", { mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), @@ -49,7 +41,6 @@ export const venueAttachmentsTable = pgTable( index().on(self.createdAt), index().on(self.creatorId), index().on(self.venueId), - uniqueIndex().on(self.position, self.venueId), ], ); diff --git a/src/drizzle/tables/venueBookings.ts b/src/drizzle/tables/venueBookings.ts index 33c431e906..12bc97d50e 100755 --- a/src/drizzle/tables/venueBookings.ts +++ b/src/drizzle/tables/venueBookings.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -33,7 +33,9 @@ export const venueBookingsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), diff --git a/src/drizzle/tables/venues.ts b/src/drizzle/tables/venues.ts index 1fbc85c427..490c9aa1d8 100755 --- a/src/drizzle/tables/venues.ts +++ b/src/drizzle/tables/venues.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, integer, @@ -45,7 +45,9 @@ export const venuesTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id, {}), }, diff --git a/src/drizzle/tables/volunteerGroupAssignments.ts b/src/drizzle/tables/volunteerGroupAssignments.ts index db2df085eb..879e5b2e38 100755 --- a/src/drizzle/tables/volunteerGroupAssignments.ts +++ b/src/drizzle/tables/volunteerGroupAssignments.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, pgTable, @@ -40,7 +40,9 @@ export const volunteerGroupAssignmentsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id), }, diff --git a/src/drizzle/tables/volunteerGroups.ts b/src/drizzle/tables/volunteerGroups.ts index 1092fbf433..adae082fed 100755 --- a/src/drizzle/tables/volunteerGroups.ts +++ b/src/drizzle/tables/volunteerGroups.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { index, integer, @@ -44,7 +44,9 @@ export const volunteerGroupsTable = pgTable( mode: "date", precision: 3, withTimezone: true, - }), + }) + .$defaultFn(() => sql`${null}`) + .$onUpdate(() => new Date()), updaterId: uuid("updater_id").references(() => usersTable.id), }, diff --git a/src/graphql/builder.ts b/src/graphql/builder.ts index b2c9a70a14..8e8b14f270 100644 --- a/src/graphql/builder.ts +++ b/src/graphql/builder.ts @@ -1,4 +1,5 @@ import SchemaBuilder from "@pothos/core"; +import relayPlugin from "@pothos/plugin-relay"; import type { GraphQLContext } from "~/src/graphql/context"; import type { CustomScalars } from "./scalars/index"; @@ -8,4 +9,7 @@ import type { CustomScalars } from "./scalars/index"; export const builder = new SchemaBuilder<{ Context: GraphQLContext; Scalars: CustomScalars; -}>({}); +}>({ + plugins: [relayPlugin], + relay: {}, +}); diff --git a/src/graphql/enums/AdvertisementAttachmentType.ts b/src/graphql/enums/AdvertisementAttachmentType.ts new file mode 100755 index 0000000000..3d73b5e6b0 --- /dev/null +++ b/src/graphql/enums/AdvertisementAttachmentType.ts @@ -0,0 +1,10 @@ +import { advertisementAttachmentTypeEnum } from "~/src/drizzle/enums/advertisementAttachmentType"; +import { builder } from "~/src/graphql/builder"; + +export const AdvertisementAttachmentType = builder.enumType( + "AdvertisementAttachmentType", + { + description: "", + values: advertisementAttachmentTypeEnum.enumValues, + }, +); diff --git a/src/graphql/enums/AdvertisementType.ts b/src/graphql/enums/AdvertisementType.ts new file mode 100755 index 0000000000..acf866e6cc --- /dev/null +++ b/src/graphql/enums/AdvertisementType.ts @@ -0,0 +1,7 @@ +import { advertisementTypeEnum } from "~/src/drizzle/enums/advertisementType"; +import { builder } from "~/src/graphql/builder"; + +export const AdvertisementType = builder.enumType("AdvertisementType", { + description: "", + values: advertisementTypeEnum.enumValues, +}); diff --git a/src/graphql/enums/CommentVoteType.ts b/src/graphql/enums/CommentVoteType.ts new file mode 100755 index 0000000000..6c56ed2a50 --- /dev/null +++ b/src/graphql/enums/CommentVoteType.ts @@ -0,0 +1,7 @@ +import { commmentVoteTypeEnum } from "~/src/drizzle/enums/commentVoteType"; +import { builder } from "~/src/graphql/builder"; + +export const CommentVoteType = builder.enumType("CommentVoteType", { + description: "", + values: commmentVoteTypeEnum.enumValues, +}); diff --git a/src/graphql/enums/OrganizationMembershipRole.ts b/src/graphql/enums/OrganizationMembershipRole.ts new file mode 100755 index 0000000000..ae4484183a --- /dev/null +++ b/src/graphql/enums/OrganizationMembershipRole.ts @@ -0,0 +1,10 @@ +import { organizationMembershipRoleEnum } from "~/src/drizzle/enums/organizationMembershipRole"; +import { builder } from "~/src/graphql/builder"; + +export const OrganizationMembershipRole = builder.enumType( + "OrganizationMembershipRole", + { + description: "", + values: organizationMembershipRoleEnum.enumValues, + }, +); diff --git a/src/graphql/enums/PostAttachmentType.ts b/src/graphql/enums/PostAttachmentType.ts new file mode 100755 index 0000000000..02790e1d32 --- /dev/null +++ b/src/graphql/enums/PostAttachmentType.ts @@ -0,0 +1,7 @@ +import { postAttachmentTypeEnum } from "~/src/drizzle/enums/postAttachmentType"; +import { builder } from "~/src/graphql/builder"; + +export const PostAttachmentType = builder.enumType("PostAttachmentType", { + description: "", + values: postAttachmentTypeEnum.enumValues, +}); diff --git a/src/graphql/enums/PostVoteType.ts b/src/graphql/enums/PostVoteType.ts new file mode 100755 index 0000000000..dbf451af17 --- /dev/null +++ b/src/graphql/enums/PostVoteType.ts @@ -0,0 +1,7 @@ +import { postVoteTypeEnum } from "~/src/drizzle/enums/postVoteType"; +import { builder } from "~/src/graphql/builder"; + +export const PostVoteType = builder.enumType("PostVoteType", { + description: "", + values: postVoteTypeEnum.enumValues, +}); diff --git a/src/graphql/enums/index.ts b/src/graphql/enums/index.ts index 81df09ff73..fa58582221 100644 --- a/src/graphql/enums/index.ts +++ b/src/graphql/enums/index.ts @@ -1,4 +1,10 @@ +import "./AdvertisementAttachmentType"; +import "./AdvertisementType"; +import "./CommentVoteType"; import "./Iso3166Alpha2CountryCode"; +import "./OrganizationMembershipRole"; +import "./PostAttachmentType"; +import "./PostVoteType"; import "./UserEducationGrade"; import "./UserEmploymentStatus"; import "./UserMaritalStatus"; diff --git a/src/graphql/inputs/CreateAdvertisementAttachmentInput.ts b/src/graphql/inputs/CreateAdvertisementAttachmentInput.ts new file mode 100644 index 0000000000..7bbded4dd2 --- /dev/null +++ b/src/graphql/inputs/CreateAdvertisementAttachmentInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { advertisementAttachmentsTableInsertSchema } from "~/src/drizzle/tables/advertisementAttachments"; +import { builder } from "~/src/graphql/builder"; +import { AdvertisementAttachmentType } from "~/src/graphql/enums/AdvertisementAttachmentType"; + +export const createAdvertisementAttachmentInputSchema = + advertisementAttachmentsTableInsertSchema.pick({ + uri: true, + type: true, + }); + +export const CreateAdvertisementAttachmentInput = builder + .inputRef>( + "AdvertisementAttachmentInput", + ) + .implement({ + description: "", + fields: (t) => ({ + uri: t.string({ + description: "URI to the attachment.", + required: true, + }), + type: t.field({ + description: "Type of the attachment.", + required: true, + type: AdvertisementAttachmentType, + }), + }), + }); diff --git a/src/graphql/inputs/CreatePostAttachmentInput.ts b/src/graphql/inputs/CreatePostAttachmentInput.ts new file mode 100644 index 0000000000..4d06d3817b --- /dev/null +++ b/src/graphql/inputs/CreatePostAttachmentInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { postAttachmentsTableInsertSchema } from "~/src/drizzle/tables/postAttachments"; +import { builder } from "~/src/graphql/builder"; +import { PostAttachmentType } from "~/src/graphql/enums/PostAttachmentType"; + +export const createPostAttachmentInputSchema = + postAttachmentsTableInsertSchema.pick({ + uri: true, + type: true, + }); + +export const CreatePostAttachmentInput = builder + .inputRef>( + "PostAttachmentInput", + ) + .implement({ + description: "", + fields: (t) => ({ + uri: t.string({ + description: "URI to the attachment.", + required: true, + }), + type: t.field({ + description: "Type of the attachment.", + required: true, + type: PostAttachmentType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateAdvertisementInput.ts b/src/graphql/inputs/MutationCreateAdvertisementInput.ts new file mode 100644 index 0000000000..782f5f370f --- /dev/null +++ b/src/graphql/inputs/MutationCreateAdvertisementInput.ts @@ -0,0 +1,75 @@ +import type { z } from "zod"; +import { advertisementsTableInsertSchema } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { AdvertisementType } from "~/src/graphql/enums/AdvertisementType"; +import { + CreateAdvertisementAttachmentInput, + createAdvertisementAttachmentInputSchema, +} from "./CreateAdvertisementAttachmentInput"; + +export const mutationCreateAdvertisementInputSchema = + advertisementsTableInsertSchema + .pick({ + description: true, + endAt: true, + name: true, + organizationId: true, + startAt: true, + type: true, + }) + .extend({ + attachments: createAdvertisementAttachmentInputSchema + .array() + .min(1) + .max(20) + .optional(), + }) + .superRefine((arg, ctx) => { + if (arg.endAt.getTime() <= arg.startAt.getTime()) { + ctx.addIssue({ + code: "custom", + message: `Must be greater than the value: ${arg.endAt}`, + path: ["endAt"], + }); + } + }); + +export const MutationCreateAdvertisementInput = builder + .inputRef>( + "MutationCreateAdvertisementInput", + ) + .implement({ + description: "", + fields: (t) => ({ + attachments: t.field({ + description: "Attachments of the advertisement.", + type: t.listRef(CreateAdvertisementAttachmentInput), + }), + description: t.string({ + description: "Custom information about the advertisement.", + }), + endAt: t.field({ + description: "Date time at which the advertised event ends.", + required: true, + type: "DateTime", + }), + name: t.string({ + description: "Name of the advertisement.", + required: true, + }), + organizationId: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + startAt: t.field({ + description: "Date time at which the advertised event starts.", + required: true, + type: "DateTime", + }), + type: t.field({ + description: "Type of the advertisement.", + required: true, + type: AdvertisementType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateCommentInput.ts b/src/graphql/inputs/MutationCreateCommentInput.ts new file mode 100644 index 0000000000..91ed6dd0a0 --- /dev/null +++ b/src/graphql/inputs/MutationCreateCommentInput.ts @@ -0,0 +1,27 @@ +import type { z } from "zod"; +import { commentsTableInsertSchema } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; + +export const mutationCreateCommentInputSchema = commentsTableInsertSchema.pick({ + body: true, + postId: true, +}); + +export const MutationCreateCommentInput = builder + .inputRef>( + "MutationCreateCommentInput", + ) + .implement({ + description: "", + fields: (t) => ({ + body: t.string({ + description: "Body of the comment.", + required: true, + }), + postId: t.id({ + description: + "Global identifier of the post on which the comment is made.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateCommentVoteInput.ts b/src/graphql/inputs/MutationCreateCommentVoteInput.ts new file mode 100644 index 0000000000..5a7c31dcbd --- /dev/null +++ b/src/graphql/inputs/MutationCreateCommentVoteInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { commentVotesTableInsertSchema } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; +import { CommentVoteType } from "~/src/graphql/enums/CommentVoteType"; + +export const mutationCreateCommentVoteInputSchema = + commentVotesTableInsertSchema.pick({ + commentId: true, + type: true, + }); + +export const MutationCreateCommentVoteInput = builder + .inputRef>( + "MutationCreateCommentVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + commentId: t.id({ + description: "Global identifier of the comment that is voted.", + required: true, + }), + type: t.field({ + description: "Type of the vote.", + required: true, + type: CommentVoteType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateOrganizationInput.ts b/src/graphql/inputs/MutationCreateOrganizationInput.ts new file mode 100644 index 0000000000..19b31f3c6d --- /dev/null +++ b/src/graphql/inputs/MutationCreateOrganizationInput.ts @@ -0,0 +1,50 @@ +import type { z } from "zod"; +import { organizationsTableInsertSchema } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { Iso3166Alpha2CountryCode } from "~/src/graphql/enums/Iso3166Alpha2CountryCode"; + +export const mutationCreateOrganizationInputSchema = + organizationsTableInsertSchema.omit({ + createdAt: true, + creatorId: true, + id: true, + updatedAt: true, + updaterId: true, + }); + +export const MutationCreateOrganizationInput = builder + .inputRef>( + "MutationCreateOrganizationInput", + ) + .implement({ + description: "", + fields: (t) => ({ + address: t.string({ + description: "Address of the organization.", + }), + avatarURI: t.string({ + description: "URI to the avatar of the organization.", + }), + city: t.string({ + description: "Name of the city where the organization resides in.", + }), + countryCode: t.field({ + description: + "Country code of the country the organization is a citizen of.", + type: Iso3166Alpha2CountryCode, + }), + description: t.string({ + description: "Custom information about the organization.", + }), + name: t.string({ + description: "Name of the organization.", + required: true, + }), + postalCode: t.string({ + description: "Postal code of the organization.", + }), + state: t.string({ + description: "Name of the state the organization resides in.", + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateOrganizationMembershipInput.ts b/src/graphql/inputs/MutationCreateOrganizationMembershipInput.ts new file mode 100644 index 0000000000..d3a7823829 --- /dev/null +++ b/src/graphql/inputs/MutationCreateOrganizationMembershipInput.ts @@ -0,0 +1,36 @@ +import type { z } from "zod"; +import { organizationMembershipsTableInsertSchema } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; +import { OrganizationMembershipRole } from "~/src/graphql/enums/OrganizationMembershipRole"; + +export const mutationCreateOrganizationMembershipInputSchema = + organizationMembershipsTableInsertSchema + .pick({ + memberId: true, + organizationId: true, + }) + .extend({ + role: organizationMembershipsTableInsertSchema.shape.role.optional(), + }); + +export const MutationCreateOrganizationMembershipInput = builder + .inputRef>( + "MutationCreateOrganizationMembershipInput", + ) + .implement({ + description: "", + fields: (t) => ({ + memberId: t.id({ + description: "Global identifier of the associated user.", + required: true, + }), + organizationId: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + role: t.field({ + description: "Role assigned to the user within the organization.", + type: OrganizationMembershipRole, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreatePostInput.ts b/src/graphql/inputs/MutationCreatePostInput.ts new file mode 100644 index 0000000000..f674f488af --- /dev/null +++ b/src/graphql/inputs/MutationCreatePostInput.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; +import { postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { + CreatePostAttachmentInput, + createPostAttachmentInputSchema, +} from "./CreatePostAttachmentInput"; + +export const mutationCreatePostInputSchema = postsTableInsertSchema + .pick({ + caption: true, + organizationId: true, + }) + .extend({ + attachments: createPostAttachmentInputSchema + .array() + .min(1) + .max(20) + .optional(), + isPinned: z.boolean().optional(), + }); + +export const MutationCreatePostInput = builder + .inputRef>( + "MutationCreatePostInput", + ) + .implement({ + description: "", + fields: (t) => ({ + attachments: t.field({ + description: "Attachments of the post.", + type: t.listRef(CreatePostAttachmentInput), + }), + caption: t.string({ + description: "Caption about the post.", + required: true, + }), + isPinned: t.boolean({ + description: "Boolean to tell if the post is pinned", + }), + organizationId: t.id({ + description: + "Global identifier of the associated organization in which the post is posted.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreatePostVoteInput.ts b/src/graphql/inputs/MutationCreatePostVoteInput.ts new file mode 100644 index 0000000000..5f31522256 --- /dev/null +++ b/src/graphql/inputs/MutationCreatePostVoteInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { postVotesTableInsertSchema } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; +import { PostVoteType } from "~/src/graphql/enums/PostVoteType"; + +export const mutationCreatePostVoteInputSchema = + postVotesTableInsertSchema.pick({ + postId: true, + type: true, + }); + +export const MutationCreatePostVoteInput = builder + .inputRef>( + "MutationCreatePostVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + postId: t.id({ + description: "Global identifier of the post that is voted.", + required: true, + }), + type: t.field({ + description: "Type of the vote.", + required: true, + type: PostVoteType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationCreateTagInput.ts b/src/graphql/inputs/MutationCreateTagInput.ts new file mode 100644 index 0000000000..411ba26dcb --- /dev/null +++ b/src/graphql/inputs/MutationCreateTagInput.ts @@ -0,0 +1,36 @@ +import type { z } from "zod"; +import { tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; + +export const mutationCreateTagInputSchema = tagsTableInsertSchema.pick({ + isFolder: true, + name: true, + organizationId: true, + parentTagId: true, +}); + +export const MutationCreateTagInput = builder + .inputRef>( + "MutationCreateTagInput", + ) + .implement({ + description: "", + fields: (t) => ({ + isFolder: t.boolean({ + description: + "Boolean to tell if the tag is to be used as a tag folder.", + required: true, + }), + name: t.string({ + description: "Name of the tag.", + required: true, + }), + organizationId: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + parentTagId: t.id({ + description: "Global identifier of the parent tag.", + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteAdvertisementInput.ts b/src/graphql/inputs/MutationDeleteAdvertisementInput.ts new file mode 100644 index 0000000000..f31fb9220b --- /dev/null +++ b/src/graphql/inputs/MutationDeleteAdvertisementInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { advertisementsTableInsertSchema } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteAdvertisementInputSchema = z.object({ + id: advertisementsTableInsertSchema.shape.id.unwrap(), +}); + +export const MutationDeleteAdvertisementInput = builder + .inputRef>( + "MutationDeleteAdvertisementInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the advertisement.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteCommentInput.ts b/src/graphql/inputs/MutationDeleteCommentInput.ts new file mode 100644 index 0000000000..06039600f5 --- /dev/null +++ b/src/graphql/inputs/MutationDeleteCommentInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { commentsTableInsertSchema } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteCommentInputSchema = z.object({ + id: commentsTableInsertSchema.shape.id.unwrap(), +}); + +export const MutationDeleteCommentInput = builder + .inputRef>( + "MutationDeleteCommentInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the comment.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteCommentVoteInput.ts b/src/graphql/inputs/MutationDeleteCommentVoteInput.ts new file mode 100644 index 0000000000..74ffa0595b --- /dev/null +++ b/src/graphql/inputs/MutationDeleteCommentVoteInput.ts @@ -0,0 +1,32 @@ +import type { z } from "zod"; +import { commentVotesTableInsertSchema } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteCommentVoteInputSchema = + commentVotesTableInsertSchema + .pick({ + commentId: true, + }) + .extend({ + creatorId: commentVotesTableInsertSchema.shape.creatorId + .unwrap() + .unwrap(), + }); + +export const MutationDeleteCommentVoteInput = builder + .inputRef>( + "MutationDeleteCommentVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + commentId: t.id({ + description: "Global identifier of the comment that is voted.", + required: true, + }), + creatorId: t.id({ + description: "Global identifier of the user who voted.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteOrganizationInput.ts b/src/graphql/inputs/MutationDeleteOrganizationInput.ts new file mode 100644 index 0000000000..1bbb4ab73b --- /dev/null +++ b/src/graphql/inputs/MutationDeleteOrganizationInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { organizationsTableInsertSchema } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteOrganizationInputSchema = z.object({ + id: organizationsTableInsertSchema.shape.id.unwrap(), +}); + +export const MutationDeleteOrganizationInput = builder + .inputRef>( + "MutationDeleteOrganizationInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the organization.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteOrganizationMembershipInput.ts b/src/graphql/inputs/MutationDeleteOrganizationMembershipInput.ts new file mode 100644 index 0000000000..72ccbf4f26 --- /dev/null +++ b/src/graphql/inputs/MutationDeleteOrganizationMembershipInput.ts @@ -0,0 +1,27 @@ +import type { z } from "zod"; +import { organizationMembershipsTableInsertSchema } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteOrganizationMembershipInputSchema = + organizationMembershipsTableInsertSchema.pick({ + memberId: true, + organizationId: true, + }); + +export const MutationDeleteOrganizationMembershipInput = builder + .inputRef>( + "MutationDeleteOrganizationMembershipInput", + ) + .implement({ + description: "", + fields: (t) => ({ + memberId: t.id({ + description: "Global identifier of the associated user.", + required: true, + }), + organizationId: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeletePostInput.ts b/src/graphql/inputs/MutationDeletePostInput.ts new file mode 100644 index 0000000000..5a7092872f --- /dev/null +++ b/src/graphql/inputs/MutationDeletePostInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeletePostInputSchema = z.object({ + id: postsTableInsertSchema.shape.id.unwrap(), +}); + +export const MutationDeletePostInput = builder + .inputRef>( + "MutationDeletePostInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the post.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeletePostVoteInput.ts b/src/graphql/inputs/MutationDeletePostVoteInput.ts new file mode 100644 index 0000000000..f9e7e6081c --- /dev/null +++ b/src/graphql/inputs/MutationDeletePostVoteInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { postVotesTableInsertSchema } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeletePostVoteInputSchema = postVotesTableInsertSchema + .pick({ + postId: true, + }) + .extend({ + creatorId: postVotesTableInsertSchema.shape.creatorId.unwrap().unwrap(), + }); + +export const MutationDeletePostVoteInput = builder + .inputRef>( + "MutationDeletePostVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + creatorId: t.id({ + description: "Global identifier of the user who voted.", + required: true, + }), + postId: t.id({ + description: "Global identifier of the post that is voted.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationDeleteTagInput.ts b/src/graphql/inputs/MutationDeleteTagInput.ts new file mode 100644 index 0000000000..d1efda5abb --- /dev/null +++ b/src/graphql/inputs/MutationDeleteTagInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; + +export const mutationDeleteTagInputSchema = z.object({ + id: tagsTableInsertSchema.shape.id.unwrap(), +}); + +export const MutationDeleteTagInput = builder + .inputRef>( + "MutationDeleteTagInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the tag.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateAdvertisementInput.ts b/src/graphql/inputs/MutationUpdateAdvertisementInput.ts new file mode 100644 index 0000000000..452deab963 --- /dev/null +++ b/src/graphql/inputs/MutationUpdateAdvertisementInput.ts @@ -0,0 +1,69 @@ +import type { z } from "zod"; +import { advertisementsTableInsertSchema } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { AdvertisementType } from "~/src/graphql/enums/AdvertisementType"; + +export const mutationUpdateAdvertisementInputSchema = + advertisementsTableInsertSchema + .pick({ + description: true, + }) + .extend({ + endAt: advertisementsTableInsertSchema.shape.endAt.optional(), + id: advertisementsTableInsertSchema.shape.id.unwrap(), + name: advertisementsTableInsertSchema.shape.name.optional(), + startAt: advertisementsTableInsertSchema.shape.startAt.optional(), + type: advertisementsTableInsertSchema.shape.type.optional(), + }) + .superRefine(({ id, ...remainingArg }, ctx) => { + if (!Object.values(remainingArg).some((value) => value !== undefined)) { + ctx.addIssue({ + code: "custom", + message: "At least one optional argument must be provided.", + }); + } + + if ( + remainingArg.endAt !== undefined && + remainingArg.startAt !== undefined && + remainingArg.endAt.getTime() <= remainingArg.startAt.getTime() + ) { + ctx.addIssue({ + code: "custom", + message: `Must be greater than the value: ${remainingArg.startAt.toISOString()}.`, + path: ["endAt"], + }); + } + }); + +export const MutationUpdateAdvertisementInput = builder + .inputRef>( + "MutationUpdateAdvertisementInput", + ) + .implement({ + description: "", + fields: (t) => ({ + description: t.string({ + description: "Custom information about the advertisement.", + }), + endAt: t.field({ + description: "Date time at which the advertised event ends.", + type: "DateTime", + }), + id: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + name: t.string({ + description: "Name of the advertisement.", + }), + startAt: t.field({ + description: "Date time at which the advertised event starts.", + type: "DateTime", + }), + type: t.field({ + description: "Type of the advertisement.", + type: AdvertisementType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateCommentInput.ts b/src/graphql/inputs/MutationUpdateCommentInput.ts new file mode 100644 index 0000000000..4966bdbbbc --- /dev/null +++ b/src/graphql/inputs/MutationUpdateCommentInput.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import { commentsTableInsertSchema } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; + +export const mutationUpdateCommentInputSchema = z + .object({ + body: commentsTableInsertSchema.shape.body.optional(), + id: commentsTableInsertSchema.shape.id.unwrap(), + }) + .refine( + ({ id, ...remainingArg }) => + Object.values(remainingArg).some((value) => value !== undefined), + { + message: "At least one optional argument must be provided.", + }, + ); + +export const MutationUpdateCommentInput = builder + .inputRef>( + "MutationUpdateCommentInput", + ) + .implement({ + description: "", + fields: (t) => ({ + body: t.string({ + description: "Body of the comment.", + }), + id: t.id({ + description: "Global identifier of the comment.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateCommentVoteInput.ts b/src/graphql/inputs/MutationUpdateCommentVoteInput.ts new file mode 100644 index 0000000000..aadeae36bd --- /dev/null +++ b/src/graphql/inputs/MutationUpdateCommentVoteInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { commentVotesTableInsertSchema } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; +import { CommentVoteType } from "~/src/graphql/enums/CommentVoteType"; + +export const mutationUpdateCommentVoteInputSchema = + commentVotesTableInsertSchema.pick({ + commentId: true, + type: true, + }); + +export const MutationUpdateCommentVoteInput = builder + .inputRef>( + "MutationUpdateCommentVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + commentId: t.id({ + description: "Global identifier of the comment that is voted.", + required: true, + }), + type: t.field({ + description: "Type of the vote.", + required: true, + type: CommentVoteType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateOrganizationInput.ts b/src/graphql/inputs/MutationUpdateOrganizationInput.ts new file mode 100644 index 0000000000..349c7d8cc2 --- /dev/null +++ b/src/graphql/inputs/MutationUpdateOrganizationInput.ts @@ -0,0 +1,66 @@ +import type { z } from "zod"; +import { organizationsTableInsertSchema } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { Iso3166Alpha2CountryCode } from "~/src/graphql/enums/Iso3166Alpha2CountryCode"; + +export const mutationUpdateOrganizationInputSchema = + organizationsTableInsertSchema + .omit({ + createdAt: true, + creatorId: true, + id: true, + name: true, + updatedAt: true, + updaterId: true, + }) + .extend({ + id: organizationsTableInsertSchema.shape.id.unwrap(), + name: organizationsTableInsertSchema.shape.name.optional(), + }) + .refine( + ({ id, ...remainingArg }) => + Object.values(remainingArg).some((value) => value !== undefined), + { + message: "At least one optional argument must be provided.", + }, + ); + +export const MutationUpdateOrganizationInput = builder + .inputRef>( + "MutationUpdateOrganizationInput", + ) + .implement({ + description: "", + fields: (t) => ({ + address: t.string({ + description: "Address of the organization.", + }), + avatarURI: t.string({ + description: "URI to the avatar of the organization.", + }), + city: t.string({ + description: "Name of the city where the organization resides in.", + }), + countryCode: t.field({ + description: + "Country code of the country the organization is a citizen of.", + type: Iso3166Alpha2CountryCode, + }), + description: t.string({ + description: "Custom information about the organization.", + }), + id: t.id({ + description: "Global identifier of the organization.", + required: true, + }), + name: t.string({ + description: "Name of the organization.", + }), + postalCode: t.string({ + description: "Postal code of the organization.", + }), + state: t.string({ + description: "Name of the state the organization resides in.", + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateOrganizationMembershipInput.ts b/src/graphql/inputs/MutationUpdateOrganizationMembershipInput.ts new file mode 100644 index 0000000000..d41b11b7ad --- /dev/null +++ b/src/graphql/inputs/MutationUpdateOrganizationMembershipInput.ts @@ -0,0 +1,43 @@ +import type { z } from "zod"; +import { organizationMembershipsTableInsertSchema } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; +import { OrganizationMembershipRole } from "~/src/graphql/enums/OrganizationMembershipRole"; + +export const mutationUpdateOrganizationMembershipInputSchema = + organizationMembershipsTableInsertSchema + .pick({ + memberId: true, + organizationId: true, + }) + .extend({ + role: organizationMembershipsTableInsertSchema.shape.role.optional(), + }) + .refine( + ({ memberId, organizationId, ...remainingArg }) => + Object.values(remainingArg).some((value) => value !== undefined), + { + message: "At least one optional argument must be provided.", + }, + ); + +export const MutationUpdateOrganizationMembershipInput = builder + .inputRef>( + "MutationUpdateOrganizationMembershipInput", + ) + .implement({ + description: "", + fields: (t) => ({ + memberId: t.id({ + description: "Global identifier of the associated user.", + required: true, + }), + organizationId: t.id({ + description: "Global identifier of the associated organization.", + required: true, + }), + role: t.field({ + description: "Role assigned to the user within the organization.", + type: OrganizationMembershipRole, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdatePostInput.ts b/src/graphql/inputs/MutationUpdatePostInput.ts new file mode 100644 index 0000000000..d073a13a6e --- /dev/null +++ b/src/graphql/inputs/MutationUpdatePostInput.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import { postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; + +export const mutationUpdatePostInputSchema = z + .object({ + caption: postsTableInsertSchema.shape.caption.optional(), + id: postsTableInsertSchema.shape.id.unwrap(), + isPinned: z.boolean().optional(), + }) + .refine( + ({ id, ...remainingArg }) => + Object.values(remainingArg).some((value) => value !== undefined), + { + message: "At least one optional argument must be provided.", + }, + ); + +export const MutationUpdatePostInput = builder + .inputRef>( + "MutationUpdatePostInput", + ) + .implement({ + description: "", + fields: (t) => ({ + caption: t.string({ + description: "Caption about the post.", + }), + id: t.id({ + description: "Global identifier of the post.", + required: true, + }), + isPinned: t.boolean({ + description: "Boolean to tell if the post is pinned", + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdatePostVoteInput.ts b/src/graphql/inputs/MutationUpdatePostVoteInput.ts new file mode 100644 index 0000000000..31c1363597 --- /dev/null +++ b/src/graphql/inputs/MutationUpdatePostVoteInput.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import { postVotesTableInsertSchema } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; +import { PostVoteType } from "~/src/graphql/enums/PostVoteType"; + +export const mutationUpdatePostVoteInputSchema = + postVotesTableInsertSchema.pick({ + postId: true, + type: true, + }); + +export const MutationUpdatePostVoteInput = builder + .inputRef>( + "MutationUpdatePostVoteInput", + ) + .implement({ + description: "", + fields: (t) => ({ + postId: t.id({ + description: "Global identifier of the voted post.", + required: true, + }), + type: t.field({ + description: "Type of the vote.", + required: true, + type: PostVoteType, + }), + }), + }); diff --git a/src/graphql/inputs/MutationUpdateTagInput.ts b/src/graphql/inputs/MutationUpdateTagInput.ts new file mode 100644 index 0000000000..01e4bab3e3 --- /dev/null +++ b/src/graphql/inputs/MutationUpdateTagInput.ts @@ -0,0 +1,39 @@ +import type { z } from "zod"; +import { tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; + +export const mutationUpdateTagInputSchema = tagsTableInsertSchema + .pick({ + parentTagId: true, + }) + .extend({ + id: tagsTableInsertSchema.shape.id.unwrap(), + name: tagsTableInsertSchema.shape.name.optional(), + }) + .refine( + ({ id, ...remainingArg }) => + Object.values(remainingArg).some((value) => value !== undefined), + { + message: "At least one optional argument must be provided.", + }, + ); + +export const MutationUpdateTagInput = builder + .inputRef>( + "MutationUpdateTagInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.id({ + description: "Global identifier of the tag.", + required: true, + }), + name: t.string({ + description: "Name of the tag.", + }), + parentTagId: t.id({ + description: "Global identifier of associated parent tag.", + }), + }), + }); diff --git a/src/graphql/inputs/QueryAdvertisementInput.ts b/src/graphql/inputs/QueryAdvertisementInput.ts new file mode 100644 index 0000000000..8df021c4e9 --- /dev/null +++ b/src/graphql/inputs/QueryAdvertisementInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { advertisementsTableInsertSchema } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; + +export const queryAdvertisementInputSchema = z.object({ + id: advertisementsTableInsertSchema.shape.id.unwrap(), +}); + +export const QueryAdvertisementInput = builder + .inputRef>( + "QueryAdvertisementInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.string({ + description: "Global id of the advertisement.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/QueryCommentInput.ts b/src/graphql/inputs/QueryCommentInput.ts new file mode 100644 index 0000000000..6c4a0eb0f6 --- /dev/null +++ b/src/graphql/inputs/QueryCommentInput.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import { commentsTableInsertSchema } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; + +export const queryCommentInputSchema = z.object({ + id: commentsTableInsertSchema.shape.id.unwrap(), +}); + +export const QueryCommentInput = builder + .inputRef>("QueryCommentInput") + .implement({ + description: "", + fields: (t) => ({ + id: t.string({ + description: "Global id of the comment.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/QueryOrganizationInput.ts b/src/graphql/inputs/QueryOrganizationInput.ts new file mode 100644 index 0000000000..cf4c878641 --- /dev/null +++ b/src/graphql/inputs/QueryOrganizationInput.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { organizationsTableInsertSchema } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; + +export const queryOrganizationInputSchema = z.object({ + id: organizationsTableInsertSchema.shape.id.unwrap(), +}); + +export const QueryOrganizationInput = builder + .inputRef>( + "QueryOrganizationInput", + ) + .implement({ + description: "", + fields: (t) => ({ + id: t.string({ + description: "Global id of the organization.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/QueryPostInput.ts b/src/graphql/inputs/QueryPostInput.ts new file mode 100644 index 0000000000..e46ba1987e --- /dev/null +++ b/src/graphql/inputs/QueryPostInput.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import { postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; + +export const queryPostInputSchema = z.object({ + id: postsTableInsertSchema.shape.id.unwrap(), +}); + +export const QueryPostInput = builder + .inputRef>("QueryPostInput") + .implement({ + description: "", + fields: (t) => ({ + id: t.string({ + description: "Global id of the post.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/QueryTagInput.ts b/src/graphql/inputs/QueryTagInput.ts new file mode 100644 index 0000000000..929cc2dd37 --- /dev/null +++ b/src/graphql/inputs/QueryTagInput.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import { tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; + +export const queryTagInputSchema = z.object({ + id: tagsTableInsertSchema.shape.id.unwrap(), +}); + +export const QueryTagInput = builder + .inputRef>("QueryTagInput") + .implement({ + description: "", + fields: (t) => ({ + id: t.string({ + description: "Global id of the tag.", + required: true, + }), + }), + }); diff --git a/src/graphql/inputs/index.ts b/src/graphql/inputs/index.ts index d58bdb62dc..01445b53b0 100644 --- a/src/graphql/inputs/index.ts +++ b/src/graphql/inputs/index.ts @@ -1,7 +1,38 @@ +import "./CreateAdvertisementAttachmentInput"; +import "./CreatePostAttachmentInput"; +import "./MutationCreateAdvertisementInput"; +import "./MutationCreateCommentInput"; +import "./MutationCreateCommentVoteInput"; +import "./MutationCreateOrganizationInput"; +import "./MutationCreateOrganizationMembershipInput"; +import "./MutationCreatePostInput"; +import "./MutationCreatePostVoteInput"; +import "./MutationCreateTagInput"; import "./MutationCreateUserInput"; +import "./MutationDeleteAdvertisementInput"; +import "./MutationDeleteCommentInput"; +import "./MutationDeleteCommentVoteInput"; +import "./MutationDeleteOrganizationInput"; +import "./MutationDeleteOrganizationMembershipInput"; +import "./MutationDeletePostInput"; +import "./MutationDeletePostVoteInput"; +import "./MutationDeleteTagInput"; import "./MutationDeleteUserInput"; import "./MutationSignUpInput"; +import "./MutationUpdateAdvertisementInput"; +import "./MutationUpdateCommentInput"; +import "./MutationUpdateCommentVoteInput"; import "./MutationUpdateCurrentUserInput"; +import "./MutationUpdateOrganizationInput"; +import "./MutationUpdateOrganizationMembershipInput"; +import "./MutationUpdatePostInput"; +import "./MutationUpdatePostVoteInput"; +import "./MutationUpdateTagInput"; import "./MutationUpdateUserInput"; +import "./QueryAdvertisementInput"; +import "./QueryCommentInput"; +import "./QueryOrganizationInput"; +import "./QueryPostInput"; import "./QuerySignInInput"; +import "./QueryTagInput"; import "./QueryUserInput"; diff --git a/src/graphql/types/Advertisement/Advertisement.ts b/src/graphql/types/Advertisement/Advertisement.ts new file mode 100644 index 0000000000..3dc642ab13 --- /dev/null +++ b/src/graphql/types/Advertisement/Advertisement.ts @@ -0,0 +1,45 @@ +import type { advertisementsTable } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { AdvertisementType } from "~/src/graphql/enums/AdvertisementType"; +import { + AdvertisementAttachment, + type AdvertisementAttachment as AdvertisementAttachmentType, +} from "~/src/graphql/types/AdvertisementAttachment/AdvertisementAttachment"; + +export type Advertisement = typeof advertisementsTable.$inferSelect & { + attachments: AdvertisementAttachmentType[] | null; +}; + +export const Advertisement = builder.objectRef("Advertisement"); + +Advertisement.implement({ + description: "", + fields: (t) => ({ + attachments: t.expose("attachments", { + description: "Array of attachments.", + type: t.listRef(AdvertisementAttachment), + }), + description: t.exposeString("description", { + description: "Custom information about the advertisement.", + }), + endAt: t.expose("endAt", { + description: "Date time at the time the advertised event ends at.", + type: "DateTime", + }), + id: t.exposeID("id", { + description: "Global identifier of the advertisement.", + nullable: false, + }), + name: t.exposeString("name", { + description: "Name of the advertisement.", + }), + startAt: t.expose("startAt", { + description: "Date time at the time the advertised event starts at.", + type: "DateTime", + }), + type: t.expose("type", { + description: "Type of the attachment.", + type: AdvertisementType, + }), + }), +}); diff --git a/src/graphql/types/Advertisement/createdAt.ts b/src/graphql/types/Advertisement/createdAt.ts new file mode 100644 index 0000000000..a6b4aeda75 --- /dev/null +++ b/src/graphql/types/Advertisement/createdAt.ts @@ -0,0 +1,66 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Advertisement } from "./Advertisement"; + +Advertisement.implement({ + fields: (t) => ({ + createdAt: t.field({ + description: "Date time at the time the advertisement was created.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.createdAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Advertisement/creator.ts b/src/graphql/types/Advertisement/creator.ts new file mode 100644 index 0000000000..00396ab08b --- /dev/null +++ b/src/graphql/types/Advertisement/creator.ts @@ -0,0 +1,93 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Advertisement } from "./Advertisement"; + +Advertisement.implement({ + fields: (t) => ({ + creator: t.field({ + description: "User who created the advertisement.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.creatorId === null) { + return null; + } + + if (parent.creatorId === currentUserId) { + return currentUser; + } + + const creatorId = parent.creatorId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); + + // Creator id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a user's creator id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Advertisement/index.ts b/src/graphql/types/Advertisement/index.ts new file mode 100644 index 0000000000..a68340e898 --- /dev/null +++ b/src/graphql/types/Advertisement/index.ts @@ -0,0 +1,6 @@ +import "./Advertisement"; +import "./createdAt"; +import "./creator"; +import "./organization"; +import "./updatedAt"; +import "./updater"; diff --git a/src/graphql/types/Advertisement/organization.ts b/src/graphql/types/Advertisement/organization.ts new file mode 100644 index 0000000000..7ce121f7bb --- /dev/null +++ b/src/graphql/types/Advertisement/organization.ts @@ -0,0 +1,35 @@ +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Advertisement } from "./Advertisement"; + +Advertisement.implement({ + fields: (t) => ({ + organization: t.field({ + description: "Organization which the advertisement belongs to.", + resolve: async (parent, _args, ctx) => { + const existingOrganization = + await ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parent.organizationId), + }); + + // Organziation id existing but the associated organization not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingOrganization === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a tag's organization id that isn't null.", + ); + + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), + }), +}); diff --git a/src/graphql/types/Advertisement/updatedAt.ts b/src/graphql/types/Advertisement/updatedAt.ts new file mode 100644 index 0000000000..13e12bc303 --- /dev/null +++ b/src/graphql/types/Advertisement/updatedAt.ts @@ -0,0 +1,66 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Advertisement } from "./Advertisement"; + +Advertisement.implement({ + fields: (t) => ({ + updatedAt: t.field({ + description: "Date time at the time the advertisement was last updated.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.updatedAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Advertisement/updater.ts b/src/graphql/types/Advertisement/updater.ts new file mode 100644 index 0000000000..ca6f4ef7e2 --- /dev/null +++ b/src/graphql/types/Advertisement/updater.ts @@ -0,0 +1,93 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Advertisement } from "./Advertisement"; + +Advertisement.implement({ + fields: (t) => ({ + updater: t.field({ + description: "User who last updated the advertisement.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.updaterId === null) { + return null; + } + + if (parent.updaterId === currentUserId) { + return currentUser; + } + + const updaterId = parent.updaterId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a user's updater id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/AdvertisementAttachment/AdvertisementAttachment.ts b/src/graphql/types/AdvertisementAttachment/AdvertisementAttachment.ts new file mode 100644 index 0000000000..f531ec0a8a --- /dev/null +++ b/src/graphql/types/AdvertisementAttachment/AdvertisementAttachment.ts @@ -0,0 +1,22 @@ +import type { advertisementAttachmentsTable } from "~/src/drizzle/tables/advertisementAttachments"; +import { builder } from "~/src/graphql/builder"; +import { AdvertisementAttachmentType } from "~/src/graphql/enums/AdvertisementAttachmentType"; + +export type AdvertisementAttachment = + typeof advertisementAttachmentsTable.$inferSelect; + +export const AdvertisementAttachment = + builder.objectRef("AdvertisementAttachment"); + +AdvertisementAttachment.implement({ + description: "", + fields: (t) => ({ + type: t.expose("type", { + description: "Type of the attachment.", + type: AdvertisementAttachmentType, + }), + uri: t.exposeString("uri", { + description: "URI to the attachment.", + }), + }), +}); diff --git a/src/graphql/types/AdvertisementAttachment/index.ts b/src/graphql/types/AdvertisementAttachment/index.ts new file mode 100644 index 0000000000..c02766c744 --- /dev/null +++ b/src/graphql/types/AdvertisementAttachment/index.ts @@ -0,0 +1 @@ +import "./AdvertisementAttachment"; diff --git a/src/graphql/types/Comment/Comment.ts b/src/graphql/types/Comment/Comment.ts new file mode 100644 index 0000000000..bd96f74233 --- /dev/null +++ b/src/graphql/types/Comment/Comment.ts @@ -0,0 +1,27 @@ +import type { commentsTable } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; + +export type Comment = typeof commentsTable.$inferSelect; + +export const Comment = builder.objectRef("Comment"); + +Comment.implement({ + description: "", + fields: (t) => ({ + body: t.exposeString("body", { + description: "Body of the comment.", + }), + createdAt: t.expose("createdAt", { + description: "Date time at the time the comment was created.", + type: "DateTime", + }), + id: t.exposeID("id", { + description: "Global identifier of the comment.", + nullable: false, + }), + updatedAt: t.expose("updatedAt", { + description: "Date time at the time the comment was last updated.", + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Comment/creator.ts b/src/graphql/types/Comment/creator.ts new file mode 100644 index 0000000000..09e07a9111 --- /dev/null +++ b/src/graphql/types/Comment/creator.ts @@ -0,0 +1,40 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Comment } from "./Comment"; + +Comment.implement({ + fields: (t) => ({ + creator: t.field({ + description: "User who created the comment.", + resolve: async (parent, _args, ctx) => { + if (parent.creatorId === null) { + return null; + } + + const creatorId = parent.creatorId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); + + // Creator id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a comment's creator id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Comment/downVoters.ts b/src/graphql/types/Comment/downVoters.ts new file mode 100644 index 0000000000..86179523bb --- /dev/null +++ b/src/graphql/types/Comment/downVoters.ts @@ -0,0 +1,225 @@ +import { + type SQL, + and, + asc, + desc, + eq, + exists, + gt, + lt, + ne, + or, + sql, +} from "drizzle-orm"; +import { z } from "zod"; +import { + commentVotesTable, + commentVotesTableInsertSchema, +} from "~/src/drizzle/tables/commentVotes"; +import { User } from "~/src/graphql/types/User/User"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Comment } from "./Comment"; + +const downVotersArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + createdAt: commentVotesTableInsertSchema.shape.createdAt.unwrap(), + creatorId: commentVotesTableInsertSchema.shape.creatorId.unwrap().unwrap(), +}); + +Comment.implement({ + fields: (t) => ({ + downVoters: t.connection( + { + description: + "GraphQL connection to traverse through the voters that down voted the comment.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = downVotersArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [ + asc(commentVotesTable.createdAt), + asc(commentVotesTable.creatorId), + ] + : [ + desc(commentVotesTable.createdAt), + desc(commentVotesTable.creatorId), + ]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + eq(commentVotesTable.creatorId, cursor.creatorId), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + ), + ), + ), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + or( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + gt(commentVotesTable.creatorId, cursor.creatorId), + ), + gt(commentVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(commentVotesTable.creatorId, sql`${null}`), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + eq(commentVotesTable.creatorId, cursor.creatorId), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + ), + ), + ), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + or( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + lt(commentVotesTable.creatorId, cursor.creatorId), + ), + lt(commentVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(commentVotesTable.creatorId, sql`${null}`), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + ); + } + } + + const commentVotes = + await ctx.drizzleClient.query.commentVotesTable.findMany({ + columns: { + createdAt: true, + creatorId: true, + }, + limit, + orderBy, + with: { + creator: true, + }, + where, + }); + + if (cursor !== undefined && commentVotes.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (vote) => + Buffer.from( + JSON.stringify({ + createdAt: vote.createdAt, + creatorId: vote.creatorId, + }), + ).toString("base64url"), + createNode: (vote) => vote.creator, + parsedArgs, + rawNodes: commentVotes.filter( + ( + vote, + ): vote is typeof vote & { + creator: NonNullable<(typeof vote)["creator"]>; + } => vote.creator !== null, + ), + }); + }, + type: User, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Comment/downVotesCount.ts b/src/graphql/types/Comment/downVotesCount.ts new file mode 100644 index 0000000000..3857ae94aa --- /dev/null +++ b/src/graphql/types/Comment/downVotesCount.ts @@ -0,0 +1,31 @@ +import { and, count, eq } from "drizzle-orm"; +import { commentVotesTable } from "~/src/drizzle/tables/commentVotes"; +import { Comment } from "./Comment"; + +Comment.implement({ + fields: (t) => ({ + downVotesCount: t.field({ + description: "Total number of down votes on the comment.", + resolve: async (parent, _args, ctx) => { + const [commentVotesCount] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "down_vote"), + ), + ); + + if (commentVotesCount === undefined) { + return 0; + } + + return commentVotesCount.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Comment/index.ts b/src/graphql/types/Comment/index.ts new file mode 100644 index 0000000000..b9b62636c2 --- /dev/null +++ b/src/graphql/types/Comment/index.ts @@ -0,0 +1,8 @@ +import "./Comment"; +import "./creator"; +import "./downVoters"; +import "./downVotesCount"; +import "./post"; +import "./upVoters"; +import "./upVotesCount"; +import "./updater"; diff --git a/src/graphql/types/Comment/post.ts b/src/graphql/types/Comment/post.ts new file mode 100644 index 0000000000..02d10c64d1 --- /dev/null +++ b/src/graphql/types/Comment/post.ts @@ -0,0 +1,41 @@ +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Comment } from "./Comment"; + +Comment.implement({ + fields: (t) => ({ + post: t.field({ + description: "Post which the post belongs to.", + resolve: async (parent, _args, ctx) => { + const existingPost = await ctx.drizzleClient.query.postsTable.findFirst( + { + with: { + postAttachmentsWherePost: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parent.postId), + }, + ); + + // Post id existing but the associated post not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingPost === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a comment's post id that isn't null.", + ); + + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return Object.assign(existingPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), + }), +}); diff --git a/src/graphql/types/Comment/upVoters.ts b/src/graphql/types/Comment/upVoters.ts new file mode 100644 index 0000000000..d0ff4ae67f --- /dev/null +++ b/src/graphql/types/Comment/upVoters.ts @@ -0,0 +1,225 @@ +import { + type SQL, + and, + asc, + desc, + eq, + exists, + gt, + lt, + ne, + or, + sql, +} from "drizzle-orm"; +import { z } from "zod"; +import { + commentVotesTable, + commentVotesTableInsertSchema, +} from "~/src/drizzle/tables/commentVotes"; +import { User } from "~/src/graphql/types/User/User"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Comment } from "./Comment"; + +const upVotersArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + createdAt: commentVotesTableInsertSchema.shape.createdAt.unwrap(), + creatorId: commentVotesTableInsertSchema.shape.creatorId.unwrap().unwrap(), +}); + +Comment.implement({ + fields: (t) => ({ + upVoters: t.connection( + { + description: + "GraphQL connection to traverse through the voters that up voted the comment.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = upVotersArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [ + asc(commentVotesTable.createdAt), + asc(commentVotesTable.creatorId), + ] + : [ + desc(commentVotesTable.createdAt), + desc(commentVotesTable.creatorId), + ]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + eq(commentVotesTable.creatorId, cursor.creatorId), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + ), + ), + ), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + or( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + gt(commentVotesTable.creatorId, cursor.creatorId), + ), + gt(commentVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(commentVotesTable.creatorId, sql`${null}`), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + eq(commentVotesTable.creatorId, cursor.creatorId), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + ), + ), + ), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + or( + and( + eq(commentVotesTable.createdAt, cursor.createdAt), + lt(commentVotesTable.creatorId, cursor.creatorId), + ), + lt(commentVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(commentVotesTable.creatorId, sql`${null}`), + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + ); + } + } + + const commentVotes = + await ctx.drizzleClient.query.commentVotesTable.findMany({ + columns: { + createdAt: true, + creatorId: true, + }, + limit, + orderBy, + with: { + creator: true, + }, + where, + }); + + if (cursor !== undefined && commentVotes.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (vote) => + Buffer.from( + JSON.stringify({ + createdAt: vote.createdAt, + creatorId: vote.creatorId, + }), + ).toString("base64url"), + createNode: (vote) => vote.creator, + parsedArgs, + rawNodes: commentVotes.filter( + ( + vote, + ): vote is typeof vote & { + creator: NonNullable<(typeof vote)["creator"]>; + } => vote.creator !== null, + ), + }); + }, + type: User, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Comment/upVotesCount.ts b/src/graphql/types/Comment/upVotesCount.ts new file mode 100644 index 0000000000..f8e948a17f --- /dev/null +++ b/src/graphql/types/Comment/upVotesCount.ts @@ -0,0 +1,31 @@ +import { and, count, eq } from "drizzle-orm"; +import { commentVotesTable } from "~/src/drizzle/tables/commentVotes"; +import { Comment } from "./Comment"; + +Comment.implement({ + fields: (t) => ({ + upVotesCount: t.field({ + description: "Total number of up votes on the comment.", + resolve: async (parent, _args, ctx) => { + const [commentVotesCount] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(commentVotesTable) + .where( + and( + eq(commentVotesTable.commentId, parent.id), + eq(commentVotesTable.type, "up_vote"), + ), + ); + + if (commentVotesCount === undefined) { + return 0; + } + + return commentVotesCount.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Comment/updater.ts b/src/graphql/types/Comment/updater.ts new file mode 100644 index 0000000000..13bbee1aa2 --- /dev/null +++ b/src/graphql/types/Comment/updater.ts @@ -0,0 +1,119 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Comment } from "./Comment"; + +Comment.implement({ + fields: (t) => ({ + updater: t.field({ + description: "User who last updated the comment.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parent.postId), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + // Post id existing but the associated post not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingPost === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a comment's post id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.updaterId === null) { + return null; + } + + if (parent.updaterId === currentUserId) { + return currentUser; + } + + const updaterId = parent.updaterId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a comment's updater id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Mutation/createAdvertisement.ts b/src/graphql/types/Mutation/createAdvertisement.ts new file mode 100644 index 0000000000..b6f29c77dc --- /dev/null +++ b/src/graphql/types/Mutation/createAdvertisement.ts @@ -0,0 +1,208 @@ +import { z } from "zod"; +import { advertisementAttachmentsTable } from "~/src/drizzle/tables/advertisementAttachments"; +import { advertisementsTable } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateAdvertisementInput, + mutationCreateAdvertisementInputSchema, +} from "~/src/graphql/inputs/MutationCreateAdvertisementInput"; +import { Advertisement } from "~/src/graphql/types/Advertisement/Advertisement"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateAdvertisementArgumentsSchema = z.object({ + input: mutationCreateAdvertisementInputSchema, +}); + +builder.mutationField("createAdvertisement", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateAdvertisementInput, + }), + }, + description: "Mutation field to create an advertisement.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateAdvertisementArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingOrganization] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq( + fields.organizationId, + parsedArgs.input.organizationId, + ), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + columns: {}, + with: { + advertisementsWhereOrganization: { + columns: { + type: true, + }, + where: (fields, operators) => + operators.eq(fields.name, parsedArgs.input.name), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const existingAdvertisementWithName = + existingOrganization.advertisementsWhereOrganization[0]; + + if (existingAdvertisementWithName !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "name"], + message: "This name is not available.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + return await ctx.drizzleClient.transaction(async (tx) => { + const [createdAdvertisement] = await tx + .insert(advertisementsTable) + .values({ + creatorId: currentUserId, + description: parsedArgs.input.description, + endAt: parsedArgs.input.endAt, + name: parsedArgs.input.name, + organizationId: parsedArgs.input.organizationId, + startAt: parsedArgs.input.startAt, + type: parsedArgs.input.type, + }) + .returning(); + + // Inserted advertisement not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdAdvertisement === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + if (parsedArgs.input.attachments !== undefined) { + const createdAdvertisementAttachments = await tx + .insert(advertisementAttachmentsTable) + .values( + parsedArgs.input.attachments.map((attachment) => ({ + advertisementId: createdAdvertisement.id, + creatorId: currentUserId, + type: attachment.type, + uri: attachment.uri, + })), + ) + .returning(); + + return Object.assign(createdAdvertisement, { + attachments: createdAdvertisementAttachments, + }); + } + + return Object.assign(createdAdvertisement, { + attachments: [], + }); + }); + }, + type: Advertisement, + }), +); diff --git a/src/graphql/types/Mutation/createComment.ts b/src/graphql/types/Mutation/createComment.ts new file mode 100644 index 0000000000..a9387682ef --- /dev/null +++ b/src/graphql/types/Mutation/createComment.ts @@ -0,0 +1,154 @@ +import { z } from "zod"; +import { commentsTable } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateCommentInput, + mutationCreateCommentInputSchema, +} from "~/src/graphql/inputs/MutationCreateCommentInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateCommentArgumentsSchema = z.object({ + input: mutationCreateCommentInputSchema, +}); + +builder.mutationField("createComment", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateCommentInput, + }), + }, + description: "Mutation field to create a comment.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateCommentArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.postId), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [createdComment] = await ctx.drizzleClient + .insert(commentsTable) + .values({ + body: parsedArgs.input.body, + creatorId: currentUserId, + postId: parsedArgs.input.postId, + }) + .returning(); + + // Inserted comment not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdComment === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return createdComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/createCommentVote.ts b/src/graphql/types/Mutation/createCommentVote.ts new file mode 100644 index 0000000000..acb97b7007 --- /dev/null +++ b/src/graphql/types/Mutation/createCommentVote.ts @@ -0,0 +1,183 @@ +import { z } from "zod"; +import { commentVotesTable } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateCommentVoteInput, + mutationCreateCommentVoteInputSchema, +} from "~/src/graphql/inputs/MutationCreateCommentVoteInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateCommentVoteArgumentsSchema = z.object({ + input: mutationCreateCommentVoteInputSchema, +}); + +builder.mutationField("createCommentVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateCommentVoteInput, + }), + }, + description: "Mutation field to create a comment vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateCommentVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingComment, existingCommentVote] = + await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.commentId), + }), + ctx.drizzleClient.query.commentVotesTable.findFirst({ + where: (fields, operators) => + operators.and( + operators.eq(fields.commentId, parsedArgs.input.commentId), + operators.eq(fields.creatorId, currentUserId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingCommentVote !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "commentId"], + message: "This comment is already voted.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [createdCommentVote] = await ctx.drizzleClient + .insert(commentVotesTable) + .values({ + creatorId: currentUserId, + commentId: parsedArgs.input.commentId, + type: parsedArgs.input.type, + }) + .returning(); + + // Inserted comment vote not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdCommentVote === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/createOrganization.ts b/src/graphql/types/Mutation/createOrganization.ts new file mode 100644 index 0000000000..d158377a58 --- /dev/null +++ b/src/graphql/types/Mutation/createOrganization.ts @@ -0,0 +1,135 @@ +import { z } from "zod"; +import { organizationsTable } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateOrganizationInput, + mutationCreateOrganizationInputSchema, +} from "~/src/graphql/inputs/MutationCreateOrganizationInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateOrganizationArgumentsSchema = z.object({ + input: mutationCreateOrganizationInputSchema, +}); + +builder.mutationField("createOrganization", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateOrganizationInput, + }), + }, + description: "Mutation field to create an organization.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateOrganizationArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (currentUser.role !== "administrator") { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const existingOrganizationWithName = + await ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.name, parsedArgs.input.name), + }); + + if (existingOrganizationWithName !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "name"], + message: "This name is not available.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + const [createdOrganization] = await ctx.drizzleClient + .insert(organizationsTable) + .values({ + address: parsedArgs.input.address, + avatarURI: parsedArgs.input.avatarURI, + city: parsedArgs.input.city, + countryCode: parsedArgs.input.countryCode, + description: parsedArgs.input.description, + creatorId: currentUserId, + name: parsedArgs.input.name, + postalCode: parsedArgs.input.postalCode, + state: parsedArgs.input.state, + }) + .returning(); + + // Inserted organization not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdOrganization === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return createdOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/createOrganizationMembership.ts b/src/graphql/types/Mutation/createOrganizationMembership.ts new file mode 100644 index 0000000000..c22bff6707 --- /dev/null +++ b/src/graphql/types/Mutation/createOrganizationMembership.ts @@ -0,0 +1,220 @@ +import { z } from "zod"; +import { organizationMembershipsTable } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateOrganizationMembershipInput, + mutationCreateOrganizationMembershipInputSchema, +} from "~/src/graphql/inputs/MutationCreateOrganizationMembershipInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { getKeyPathsWithNonUndefinedValues } from "~/src/utilities/getKeyPathsWithNonUndefinedValues"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateOrganizationMembershipArgumentsSchema = z.object({ + input: mutationCreateOrganizationMembershipInputSchema, +}); + +builder.mutationField("createOrganizationMembership", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateOrganizationMembershipInput, + }), + }, + description: "Mutation field to create an organization membership.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateOrganizationMembershipArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [ + currentUser, + existingMember, + existingOrganization, + existingOrganizationMembership, + ] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.memberId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ctx.drizzleClient.query.organizationMembershipsTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.and( + operators.eq(fields.memberId, parsedArgs.input.memberId), + operators.eq( + fields.organizationId, + parsedArgs.input.organizationId, + ), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingOrganization === undefined && existingMember === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingMember === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganizationMembership !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "memberId"], + message: + "This user already has the membership of the associated organization.", + }, + { + argumentPath: ["input", "organizationId"], + message: "This organization already has the associated member.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + if (currentUser.role !== "administrator") { + const unauthorizedArgumentPaths = getKeyPathsWithNonUndefinedValues({ + keyPaths: [["input", "role"]], + object: parsedArgs, + }); + + if (unauthorizedArgumentPaths.length !== 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_arguments", + issues: unauthorizedArgumentPaths.map((argumentPath) => ({ + argumentPath, + })), + }, + message: + "You are not authorized to perform this action with the provided arguments.", + }); + } + } + + const [createdOrganizationMembership] = await ctx.drizzleClient + .insert(organizationMembershipsTable) + .values({ + creatorId: currentUserId, + memberId: parsedArgs.input.memberId, + organizationId: parsedArgs.input.organizationId, + role: + parsedArgs.input.role === undefined + ? "regular" + : parsedArgs.input.role, + }) + .returning(); + + // Inserted organization membership not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdOrganizationMembership === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/createPost.ts b/src/graphql/types/Mutation/createPost.ts new file mode 100644 index 0000000000..bc9714e294 --- /dev/null +++ b/src/graphql/types/Mutation/createPost.ts @@ -0,0 +1,197 @@ +import { z } from "zod"; +import { postAttachmentsTable } from "~/src/drizzle/tables/postAttachments"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreatePostInput, + mutationCreatePostInputSchema, +} from "~/src/graphql/inputs/MutationCreatePostInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { getKeyPathsWithNonUndefinedValues } from "~/src/utilities/getKeyPathsWithNonUndefinedValues"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreatePostArgumentsSchema = z.object({ + input: mutationCreatePostInputSchema, +}); + +builder.mutationField("createPost", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreatePostInput, + }), + }, + description: "Mutation field to create a post.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreatePostArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingOrganization] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (currentUser.role !== "administrator") { + const currentUserOrganizationMembership = + existingOrganization.organizationMembershipsWhereOrganization[0]; + + if (currentUserOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + if (currentUserOrganizationMembership.role !== "administrator") { + const unauthorizedArgumentPaths = getKeyPathsWithNonUndefinedValues({ + keyPaths: [["input", "isPinned"]], + object: parsedArgs, + }); + + if (unauthorizedArgumentPaths.length !== 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_arguments", + issues: unauthorizedArgumentPaths.map((argumentPath) => ({ + argumentPath, + })), + }, + message: + "You are not authorized to perform this action with the provided arguments.", + }); + } + } + } + + return await ctx.drizzleClient.transaction(async (tx) => { + const [createdPost] = await tx + .insert(postsTable) + .values({ + creatorId: currentUserId, + caption: parsedArgs.input.caption, + pinnedAt: + parsedArgs.input.isPinned === undefined || + parsedArgs.input.isPinned === false + ? undefined + : new Date(), + organizationId: parsedArgs.input.organizationId, + }) + .returning(); + + // Inserted post not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdPost === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + if (parsedArgs.input.attachments !== undefined) { + const createdPostAttachments = await tx + .insert(postAttachmentsTable) + .values( + parsedArgs.input.attachments.map((attachment) => ({ + creatorId: currentUserId, + postId: createdPost.id, + type: attachment.type, + uri: attachment.uri, + })), + ) + .returning(); + + return Object.assign(createdPost, { + attachments: createdPostAttachments, + }); + } + + return Object.assign(createdPost, { + attachments: [], + }); + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/createPostVote.ts b/src/graphql/types/Mutation/createPostVote.ts new file mode 100644 index 0000000000..08f0e47d46 --- /dev/null +++ b/src/graphql/types/Mutation/createPostVote.ts @@ -0,0 +1,179 @@ +import { z } from "zod"; +import { postVotesTable } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreatePostVoteInput, + mutationCreatePostVoteInputSchema, +} from "~/src/graphql/inputs/MutationCreatePostVoteInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreatePostVoteArgumentsSchema = z.object({ + input: mutationCreatePostVoteInputSchema, +}); + +builder.mutationField("createPostVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreatePostVoteInput, + }), + }, + description: "Mutation field to create a post vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreatePostVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost, existingPostVote] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + postAttachmentsWherePost: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.postId), + }), + ctx.drizzleClient.query.postVotesTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.creatorId, currentUserId), + operators.eq(fields.postId, parsedArgs.input.postId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingPostVote !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "postId"], + message: "You have already voted this post.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [createdPostVote] = await ctx.drizzleClient + .insert(postVotesTable) + .values({ + creatorId: currentUserId, + postId: parsedArgs.input.postId, + type: parsedArgs.input.type, + }) + .returning(); + + // Inserted post vote not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdPostVote === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(existingPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/createTag.ts b/src/graphql/types/Mutation/createTag.ts new file mode 100644 index 0000000000..0bec5f7e08 --- /dev/null +++ b/src/graphql/types/Mutation/createTag.ts @@ -0,0 +1,243 @@ +import { z } from "zod"; +import { tagsTable } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; +import { + MutationCreateTagInput, + mutationCreateTagInputSchema, +} from "~/src/graphql/inputs/MutationCreateTagInput"; +import { Tag } from "~/src/graphql/types/Tag/Tag"; +import { isNotNullish } from "~/src/utilities/isNotNullish"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationCreateTagArgumentsSchema = z.object({ + input: mutationCreateTagInputSchema, +}); + +builder.mutationField("createTag", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationCreateTagInput, + }), + }, + description: "Mutation field to create a tag.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationCreateTagArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingOrganization, existingTag] = + await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ctx.drizzleClient.query.tagsTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.isFolder, parsedArgs.input.isFolder), + operators.eq(fields.name, parsedArgs.input.name), + operators.eq( + fields.organizationId, + parsedArgs.input.organizationId, + ), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingTag !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "name"], + message: "This name is not available.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + if (isNotNullish(parsedArgs.input.parentTagId)) { + const parentTagId = parsedArgs.input.parentTagId; + + const existingParentTag = + await ctx.drizzleClient.query.tagsTable.findFirst({ + columns: { + isFolder: true, + organizationId: true, + }, + where: (fields, operators) => operators.eq(fields.id, parentTagId), + }); + + if (existingParentTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "parentTagId"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + if ( + existingParentTag.organizationId !== parsedArgs.input.organizationId + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "parentTagId"], + message: + "This tag does not belong to the associated organization.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + if (existingParentTag.isFolder !== true) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "parentTagId"], + message: "This must be a tag folder.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + } + + const currentUserOrganizationMembership = + existingOrganization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [createdTag] = await ctx.drizzleClient + .insert(tagsTable) + .values({ + creatorId: currentUserId, + isFolder: parsedArgs.input.isFolder, + parentTagId: parsedArgs.input.parentTagId, + name: parsedArgs.input.name, + organizationId: parsedArgs.input.organizationId, + }) + .returning(); + + // Inserted tag not being returned is an external defect unrelated to this code. It is very unlikely for this error to occur. + if (createdTag === undefined) { + ctx.log.error( + "Postgres insert operation unexpectedly returned an empty array instead of throwing an error.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return createdTag; + }, + type: Tag, + }), +); diff --git a/src/graphql/types/Mutation/deleteAdvertisement.ts b/src/graphql/types/Mutation/deleteAdvertisement.ts new file mode 100644 index 0000000000..474f262daa --- /dev/null +++ b/src/graphql/types/Mutation/deleteAdvertisement.ts @@ -0,0 +1,154 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { advertisementsTable } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteAdvertisementInput, + mutationDeleteAdvertisementInputSchema, +} from "~/src/graphql/inputs/MutationDeleteAdvertisementInput"; +import { Advertisement } from "~/src/graphql/types/Advertisement/Advertisement"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteAdvertisementArgumentsSchema = z.object({ + input: mutationDeleteAdvertisementInputSchema, +}); + +builder.mutationField("deleteAdvertisement", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteAdvertisementInput, + }), + }, + description: "Mutation field to delete an advertisement.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeleteAdvertisementArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingAdvertisement] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.advertisementsTable.findFirst({ + columns: {}, + with: { + advertisementAttachmentsWhereAdvertisement: true, + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingAdvertisement === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingAdvertisement.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [deletedAdvertisement] = await ctx.drizzleClient + .delete(advertisementsTable) + .where(eq(advertisementsTable.id, parsedArgs.input.id)) + .returning(); + + // Deleted advertisement not being returned means that either it was deleted or its `id` column was changed by external entities before this delete operation. + if (deletedAdvertisement === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(deletedAdvertisement, { + attachments: + existingAdvertisement.advertisementAttachmentsWhereAdvertisement, + }); + }, + type: Advertisement, + }), +); diff --git a/src/graphql/types/Mutation/deleteComment.ts b/src/graphql/types/Mutation/deleteComment.ts new file mode 100644 index 0000000000..9fffe3a23e --- /dev/null +++ b/src/graphql/types/Mutation/deleteComment.ts @@ -0,0 +1,158 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { commentsTable } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteCommentInput, + mutationDeleteCommentInputSchema, +} from "~/src/graphql/inputs/MutationDeleteCommentInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteCommentArgumentsSchema = z.object({ + input: mutationDeleteCommentInputSchema, +}); + +builder.mutationField("deleteComment", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteCommentInput, + }), + }, + description: "Mutation field to delete a comment.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeleteCommentArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingComment] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + columns: { + creatorId: true, + }, + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + existingComment.creatorId !== currentUserId)) + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [deletedComment] = await ctx.drizzleClient + .delete(commentsTable) + .where(eq(commentsTable.id, parsedArgs.input.id)) + .returning(); + + // Deleted comment not being returned means that either it was deleted or its `id` column was changed by external entities before this delete operation could take place. + if (deletedComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return deletedComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/deleteCommentVote.ts b/src/graphql/types/Mutation/deleteCommentVote.ts new file mode 100644 index 0000000000..2882968289 --- /dev/null +++ b/src/graphql/types/Mutation/deleteCommentVote.ts @@ -0,0 +1,226 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { commentVotesTable } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteCommentVoteInput, + mutationDeleteCommentVoteInputSchema, +} from "~/src/graphql/inputs/MutationDeleteCommentVoteInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteCommentVoteArgumentsSchema = z.object({ + input: mutationDeleteCommentVoteInputSchema, +}); + +builder.mutationField("deleteCommentVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteCommentVoteInput, + }), + }, + description: "Mutation field to delete a comment vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeleteCommentVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [ + currentUser, + existingComment, + existingCreator, + existingCommentVote, + ] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.commentId), + }), + ctx.drizzleClient.query.usersTable.findFirst({ + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentVotesTable.findFirst({ + where: (fields, operators) => + operators.and( + operators.eq(fields.commentId, parsedArgs.input.commentId), + operators.eq(fields.creatorId, currentUserId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined && existingCreator === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingCreator === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingCommentVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + parsedArgs.input.creatorId !== currentUserId)) + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [deletedCommentVote] = await ctx.drizzleClient + .delete(commentVotesTable) + .where( + and( + eq(commentVotesTable.commentId, parsedArgs.input.commentId), + eq(commentVotesTable.creatorId, parsedArgs.input.creatorId), + ), + ) + .returning(); + + // Deleted comment vote not being returned means that either it was deleted or its `commentId` or `creatorId` columns were changed by external entities before this delete operation could take place. + if (deletedCommentVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/deleteOrganization.ts b/src/graphql/types/Mutation/deleteOrganization.ts new file mode 100644 index 0000000000..51529f593e --- /dev/null +++ b/src/graphql/types/Mutation/deleteOrganization.ts @@ -0,0 +1,106 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { organizationsTable } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteOrganizationInput, + mutationDeleteOrganizationInputSchema, +} from "~/src/graphql/inputs/MutationDeleteOrganizationInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteOrganizationArgumentsSchema = z.object({ + input: mutationDeleteOrganizationInputSchema, +}); + +builder.mutationField("deleteOrganization", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteOrganizationInput, + }), + }, + description: "Mutation field to delete an organization.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeleteOrganizationArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (currentUser.role !== "administrator") { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const [deletedOrganization] = await ctx.drizzleClient + .delete(organizationsTable) + .where(eq(organizationsTable.id, parsedArgs.input.id)) + .returning(); + + // Deleted organization not being returned means that either it doesn't exist or it was already deleted or its `id` column was changed by external entities before this delete operation. + if (deletedOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + return deletedOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/deleteOrganizationMembership.ts b/src/graphql/types/Mutation/deleteOrganizationMembership.ts new file mode 100644 index 0000000000..897b10eaa3 --- /dev/null +++ b/src/graphql/types/Mutation/deleteOrganizationMembership.ts @@ -0,0 +1,230 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { organizationMembershipsTable } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteOrganizationMembershipInput, + mutationDeleteOrganizationMembershipInputSchema, +} from "~/src/graphql/inputs/MutationDeleteOrganizationMembershipInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteOrganizationMembershipArgumentsSchema = z.object({ + input: mutationDeleteOrganizationMembershipInputSchema, +}); + +builder.mutationField("deleteOrganizationMembership", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteOrganizationMembershipInput, + }), + }, + description: "Mutation field to delete an organization membership.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + success, + data: parsedArgs, + error, + } = mutationDeleteOrganizationMembershipArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [ + currentUser, + existingMember, + existingOrganization, + existingOrganizationMembership, + ] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.usersTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.memberId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ctx.drizzleClient.query.organizationMembershipsTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.and( + operators.eq(fields.memberId, parsedArgs.input.memberId), + operators.eq( + fields.organizationId, + parsedArgs.input.organizationId, + ), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingMember === undefined && existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingMember === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (currentUser.role !== "administrator") { + const currentUserOrganizationMembership = + existingOrganization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + currentUserId !== parsedArgs.input.memberId) + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + } + + const [deletedOrganizationMembership] = await ctx.drizzleClient + .delete(organizationMembershipsTable) + .where( + and( + eq( + organizationMembershipsTable.memberId, + parsedArgs.input.memberId, + ), + eq( + organizationMembershipsTable.organizationId, + parsedArgs.input.organizationId, + ), + ), + ) + .returning(); + + // Deleted organization membership not being returned means that either it was deleted or its `member_id` column or `organization_id` column or both were changed by external entities before this delete operation could take place. + if (deletedOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/deletePost.ts b/src/graphql/types/Mutation/deletePost.ts new file mode 100644 index 0000000000..46f235fd6c --- /dev/null +++ b/src/graphql/types/Mutation/deletePost.ts @@ -0,0 +1,156 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeletePostInput, + mutationDeletePostInputSchema, +} from "~/src/graphql/inputs/MutationDeletePostInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeletePostArgumentsSchema = z.object({ + input: mutationDeletePostInputSchema, +}); + +builder.mutationField("deletePost", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeletePostInput, + }), + }, + description: "Mutation field to delete a post.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeletePostArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + columns: { + creatorId: true, + }, + with: { + postAttachmentsWherePost: true, + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (currentUser.role !== "administrator") { + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + existingPost.creatorId !== currentUserId) + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + } + + const [deletedPost] = await ctx.drizzleClient + .delete(postsTable) + .where(eq(postsTable.id, parsedArgs.input.id)) + .returning(); + + // Deleted post not being returned means that either it was deleted or its `id` column was changed by external entities before this delete operation. + if (deletedPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(deletedPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/deletePostVote.ts b/src/graphql/types/Mutation/deletePostVote.ts new file mode 100644 index 0000000000..8af44a4876 --- /dev/null +++ b/src/graphql/types/Mutation/deletePostVote.ts @@ -0,0 +1,268 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { postVotesTable } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeletePostVoteInput, + mutationDeletePostVoteInputSchema, +} from "~/src/graphql/inputs/MutationDeletePostVoteInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeletePostVoteArgumentsSchema = z.object({ + input: mutationDeletePostVoteInputSchema, +}); + +builder.mutationField("deletePostVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeletePostVoteInput, + }), + }, + description: "Mutation field to delete a post vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeletePostVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingCreator, existingPost, existingPostVote] = + await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.usersTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.creatorId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, parsedArgs.input.creatorId), + }, + }, + }, + postAttachmentsWherePost: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.postId), + }), + ctx.drizzleClient.query.postVotesTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.creatorId, parsedArgs.input.creatorId), + operators.eq(fields.postId, parsedArgs.input.postId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined && existingCreator === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingCreator === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingPostVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + { + argumentPath: ["input", "creatorId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + currentUserId !== parsedArgs.input.creatorId)) + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "creatorId"], + }, + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + (currentUserOrganizationMembership.role !== "administrator" && + currentUserId !== parsedArgs.input.creatorId)) + ) { + if (currentUserOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "creatorId"], + }, + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + if ( + currentUserOrganizationMembership.role !== "administrator" || + currentUserId !== parsedArgs.input.creatorId + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "creatorId"], + }, + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + } + + const [deletedPostVote] = await ctx.drizzleClient + .delete(postVotesTable) + .where( + and( + eq(postVotesTable.creatorId, parsedArgs.input.creatorId), + eq(postVotesTable.postId, parsedArgs.input.postId), + ), + ) + .returning(); + + // Deleted post vote not being returned means that either it was deleted or its `creatorId` or `postId` columns were changed by external entities before this delete operation could take place. + if (deletedPostVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(existingPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/deleteTag.ts b/src/graphql/types/Mutation/deleteTag.ts new file mode 100644 index 0000000000..a92535279f --- /dev/null +++ b/src/graphql/types/Mutation/deleteTag.ts @@ -0,0 +1,144 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { tagsTable } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; +import { + MutationDeleteTagInput, + mutationDeleteTagInputSchema, +} from "~/src/graphql/inputs/MutationDeleteTagInput"; +import { Tag } from "~/src/graphql/types/Tag/Tag"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationDeleteTagArgumentsSchema = z.object({ + input: mutationDeleteTagInputSchema, +}); + +builder.mutationField("deleteTag", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationDeleteTagInput, + }), + }, + description: "Mutation field to delete a tag.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationDeleteTagArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const existingTag = await ctx.drizzleClient.query.tagsTable.findFirst({ + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }); + + if (existingTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingTag.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [deletedTag] = await ctx.drizzleClient + .delete(tagsTable) + .where(eq(tagsTable.id, parsedArgs.input.id)) + .returning(); + + // Deleted tag not being returned means that either it was deleted or its `id` column was changed by external entities before this delete operation could take place. + if (deletedTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return deletedTag; + }, + type: Tag, + }), +); diff --git a/src/graphql/types/Mutation/index.ts b/src/graphql/types/Mutation/index.ts index e76d07247f..53d060bc6b 100644 --- a/src/graphql/types/Mutation/index.ts +++ b/src/graphql/types/Mutation/index.ts @@ -1,7 +1,28 @@ import "./Mutation"; +import "./createComment"; +import "./createCommentVote"; +import "./createOrganization"; +import "./createOrganizationMembership"; +import "./createPost"; +import "./createPostVote"; +import "./createTag"; import "./createUser"; +import "./deleteComment"; +import "./deleteCommentVote"; import "./deleteCurrentUser"; +import "./deleteOrganization"; +import "./deleteOrganizationMembership"; +import "./deletePost"; +import "./deletePostVote"; +import "./deleteTag"; import "./deleteUser"; import "./signUp"; +import "./updateComment"; +import "./updateCommentVote"; import "./updateCurrentUser"; +import "./updateOrganization"; +import "./updateOrganizationMembership"; +import "./updatePost"; +import "./updatePostVote"; +import "./updateTag"; import "./updateUser"; diff --git a/src/graphql/types/Mutation/signUp.ts b/src/graphql/types/Mutation/signUp.ts index b250e48e4b..7f3867ff78 100755 --- a/src/graphql/types/Mutation/signUp.ts +++ b/src/graphql/types/Mutation/signUp.ts @@ -85,7 +85,7 @@ builder.mutationField("signUp", (t) => id: userId, isEmailAddressVerified: false, passwordHash: await hash(parsedArgs.input.password), - role: "base", + role: "regular", }) .returning(); diff --git a/src/graphql/types/Mutation/updateAdvertisement.ts b/src/graphql/types/Mutation/updateAdvertisement.ts new file mode 100644 index 0000000000..81baaedaaf --- /dev/null +++ b/src/graphql/types/Mutation/updateAdvertisement.ts @@ -0,0 +1,237 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { advertisementsTable } from "~/src/drizzle/tables/advertisements"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateAdvertisementInput, + mutationUpdateAdvertisementInputSchema, +} from "~/src/graphql/inputs/MutationUpdateAdvertisementInput"; +import { Advertisement } from "~/src/graphql/types/Advertisement/Advertisement"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateAdvertisementArgumentsSchema = z.object({ + input: mutationUpdateAdvertisementInputSchema, +}); + +builder.mutationField("updateAdvertisement", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateAdvertisementInput, + }), + }, + description: "Mutation field to update an advertisement.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdateAdvertisementArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingAdvertisement] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.advertisementsTable.findFirst({ + columns: { + endAt: true, + organizationId: true, + startAt: true, + }, + with: { + advertisementAttachmentsWhereAdvertisement: true, + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingAdvertisement === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if ( + parsedArgs.input.endAt === undefined && + parsedArgs.input.startAt !== undefined && + existingAdvertisement.endAt <= parsedArgs.input.startAt + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: [ + { + argumentPath: ["input", "startAt"], + message: `Must be smaller than the value: ${parsedArgs.input.startAt.toISOString()}`, + }, + ], + }, + message: "Invalid arguments provided.", + }); + } + + if ( + parsedArgs.input.endAt !== undefined && + parsedArgs.input.startAt === undefined && + parsedArgs.input.endAt <= existingAdvertisement.startAt + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: [ + { + argumentPath: ["input", "endAt"], + message: `Must be greater than the value: ${parsedArgs.input.endAt.toISOString()}`, + }, + ], + }, + message: "Invalid arguments provided.", + }); + } + + if (parsedArgs.input.name !== undefined) { + const name = parsedArgs.input.name; + + const existingAdvertisementWithName = + await ctx.drizzleClient.query.advertisementsTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.name, name), + operators.eq( + fields.organizationId, + existingAdvertisement.organizationId, + ), + ), + }); + + if (existingAdvertisementWithName !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "name"], + message: "This name is not available.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + } + + const currentUserOrganizationMembership = + existingAdvertisement.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [updatedAdvertisement] = await ctx.drizzleClient + .update(advertisementsTable) + .set({ + description: parsedArgs.input.description, + endAt: parsedArgs.input.endAt, + startAt: parsedArgs.input.startAt, + name: parsedArgs.input.name, + type: parsedArgs.input.type, + updaterId: currentUserId, + }) + .where(eq(advertisementsTable.id, parsedArgs.input.id)) + .returning(); + + // Updated advertisement not being returned means that either it was updated or its `id` column was changed by external entities before this update operation. + if (updatedAdvertisement === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(updatedAdvertisement, { + attachments: + existingAdvertisement.advertisementAttachmentsWhereAdvertisement, + }); + }, + type: Advertisement, + }), +); diff --git a/src/graphql/types/Mutation/updateComment.ts b/src/graphql/types/Mutation/updateComment.ts new file mode 100644 index 0000000000..36aa30e793 --- /dev/null +++ b/src/graphql/types/Mutation/updateComment.ts @@ -0,0 +1,161 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { commentsTable } from "~/src/drizzle/tables/comments"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateCommentInput, + mutationUpdateCommentInputSchema, +} from "~/src/graphql/inputs/MutationUpdateCommentInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateCommentArgumentsSchema = z.object({ + input: mutationUpdateCommentInputSchema, +}); + +builder.mutationField("updateComment", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateCommentInput, + }), + }, + description: "Mutation field to update a comment.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdateCommentArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingComment] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + columns: { + creatorId: true, + }, + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + (currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined) || + currentUserId !== existingComment.creatorId + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [updatedComment] = await ctx.drizzleClient + .update(commentsTable) + .set({ + body: parsedArgs.input.body, + updaterId: currentUserId, + }) + .where(eq(commentsTable.id, parsedArgs.input.id)) + .returning(); + + // Updated comment not being returned means that either it was already updated or its `id` column was changed by external entities before this update operation could take place. + if (updatedComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return updatedComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/updateCommentVote.ts b/src/graphql/types/Mutation/updateCommentVote.ts new file mode 100644 index 0000000000..7454dfccf4 --- /dev/null +++ b/src/graphql/types/Mutation/updateCommentVote.ts @@ -0,0 +1,172 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { commentVotesTable } from "~/src/drizzle/tables/commentVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateCommentVoteInput, + mutationUpdateCommentVoteInputSchema, +} from "~/src/graphql/inputs/MutationUpdateCommentVoteInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateCommentVoteArgumentsSchema = z.object({ + input: mutationUpdateCommentVoteInputSchema, +}); + +builder.mutationField("updateCommentVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateCommentVoteInput, + }), + }, + description: "Mutation field to update a comment vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdateCommentVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingComment, existingCommentVote] = + await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.commentId), + }), + ctx.drizzleClient.query.commentVotesTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.commentId, parsedArgs.input.commentId), + operators.eq(fields.creatorId, currentUserId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined || existingCommentVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "commentId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [updatedCommentVote] = await ctx.drizzleClient + .update(commentVotesTable) + .set({ + type: parsedArgs.input.type, + updaterId: currentUserId, + }) + .where( + and( + eq(commentVotesTable.commentId, parsedArgs.input.commentId), + eq(commentVotesTable.creatorId, currentUserId), + ), + ) + .returning(); + + // Updated comment vote not being returned means that either it was deleted or its `commentId` or `voterId` columns were changed by external entities before this delete operation could take place. + if (updatedCommentVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Mutation/updateCurrentUser.ts b/src/graphql/types/Mutation/updateCurrentUser.ts index 06c6c961a1..d33c5f35ed 100755 --- a/src/graphql/types/Mutation/updateCurrentUser.ts +++ b/src/graphql/types/Mutation/updateCurrentUser.ts @@ -1,7 +1,7 @@ import { hash } from "@node-rs/argon2"; import { eq } from "drizzle-orm"; import { z } from "zod"; -import { usersTable } from "~/src/drizzle/schema"; +import { usersTable } from "~/src/drizzle/tables/users"; import { builder } from "~/src/graphql/builder"; import { MutationUpdateCurrentUserInput, diff --git a/src/graphql/types/Mutation/updateOrganization.ts b/src/graphql/types/Mutation/updateOrganization.ts new file mode 100644 index 0000000000..4cac37aae1 --- /dev/null +++ b/src/graphql/types/Mutation/updateOrganization.ts @@ -0,0 +1,117 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { organizationsTable } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateOrganizationInput, + mutationUpdateOrganizationInputSchema, +} from "~/src/graphql/inputs/MutationUpdateOrganizationInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateOrganizationArgumentsSchema = z.object({ + input: mutationUpdateOrganizationInputSchema, +}); + +builder.mutationField("updateOrganization", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateOrganizationInput, + }), + }, + description: "Mutation field to update a organization.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + success, + data: parsedArgs, + error, + } = mutationUpdateOrganizationArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (currentUser.role !== "administrator") { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const [updatedOrganization] = await ctx.drizzleClient + .update(organizationsTable) + .set({ + address: parsedArgs.input.address, + avatarURI: parsedArgs.input.avatarURI, + city: parsedArgs.input.city, + countryCode: parsedArgs.input.countryCode, + description: parsedArgs.input.description, + name: parsedArgs.input.name, + postalCode: parsedArgs.input.postalCode, + state: parsedArgs.input.state, + updaterId: currentUserId, + }) + .where(eq(organizationsTable.id, parsedArgs.input.id)) + .returning(); + + // Updated organization not being returned means that either it doesn't exist or it was already deleted or its `id` column was changed by external entities before this update operation. + if (updatedOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + return updatedOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/updateOrganizationMembership.ts b/src/graphql/types/Mutation/updateOrganizationMembership.ts new file mode 100644 index 0000000000..742ae09a2a --- /dev/null +++ b/src/graphql/types/Mutation/updateOrganizationMembership.ts @@ -0,0 +1,234 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { organizationMembershipsTable } from "~/src/drizzle/tables/organizationMemberships"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateOrganizationMembershipInput, + mutationUpdateOrganizationMembershipInputSchema, +} from "~/src/graphql/inputs/MutationUpdateOrganizationMembershipInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateOrganizationMembershipArgumentsSchema = z.object({ + input: mutationUpdateOrganizationMembershipInputSchema, +}); + +builder.mutationField("updateOrganizationMembership", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateOrganizationMembershipInput, + }), + }, + description: "Mutation field to update an organization membership.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdateOrganizationMembershipArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [ + currentUser, + existingMember, + existingOrganization, + existingOrganizationMembership, + ] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.usersTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.memberId), + }), + ctx.drizzleClient.query.organizationsTable.findFirst({ + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.organizationId), + }), + ctx.drizzleClient.query.organizationMembershipsTable.findFirst({ + columns: { + role: true, + }, + + where: (fields, operators) => + operators.and( + operators.eq(fields.memberId, parsedArgs.input.memberId), + operators.eq( + fields.organizationId, + parsedArgs.input.organizationId, + ), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingMember === undefined && existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingMember === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (existingOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (currentUser.role !== "administrator") { + const currentUserOrganizationMembership = + existingOrganization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator" + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "memberId"], + }, + { + argumentPath: ["input", "organizationId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + } + + const [updatedOrganizationMembership] = await ctx.drizzleClient + .update(organizationMembershipsTable) + .set({ + role: parsedArgs.input.role, + updaterId: currentUserId, + }) + .where( + and( + eq( + organizationMembershipsTable.memberId, + parsedArgs.input.memberId, + ), + eq( + organizationMembershipsTable.organizationId, + parsedArgs.input.organizationId, + ), + ), + ) + .returning(); + + // Updated organization membership not being returned means that either it was deleted or its `member_id` column or `organization_id` column or both were changed by external entities before this update operation could take place. + if (updatedOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Mutation/updatePost.ts b/src/graphql/types/Mutation/updatePost.ts new file mode 100644 index 0000000000..e676487a30 --- /dev/null +++ b/src/graphql/types/Mutation/updatePost.ts @@ -0,0 +1,243 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdatePostInput, + mutationUpdatePostInputSchema, +} from "~/src/graphql/inputs/MutationUpdatePostInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { getKeyPathsWithNonUndefinedValues } from "~/src/utilities/getKeyPathsWithNonUndefinedValues"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdatePostArgumentsSchema = z.object({ + input: mutationUpdatePostInputSchema, +}); + +builder.mutationField("updatePost", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdatePostInput, + }), + }, + description: "Mutation field to update a post.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdatePostArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + columns: { + pinnedAt: true, + creatorId: true, + }, + with: { + postAttachmentsWherePost: true, + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (currentUser.role === "administrator") { + if (currentUserId !== existingPost.creatorId) { + const unauthorizedArgumentPaths = getKeyPathsWithNonUndefinedValues({ + keyPaths: [["input", "caption"]], + object: parsedArgs, + }); + + if (unauthorizedArgumentPaths.length !== 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_arguments", + issues: unauthorizedArgumentPaths.map((argumentPath) => ({ + argumentPath, + })), + }, + message: + "You are not authorized to perform this action with the provided arguments.", + }); + } + } + } else { + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if (currentUserOrganizationMembership === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + if (currentUserOrganizationMembership.role === "administrator") { + if (currentUserId !== existingPost.creatorId) { + const unauthorizedArgumentPaths = getKeyPathsWithNonUndefinedValues( + { + keyPaths: [["input", "caption"]], + object: parsedArgs, + }, + ); + + if (unauthorizedArgumentPaths.length !== 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_arguments", + issues: unauthorizedArgumentPaths.map((argumentPath) => ({ + argumentPath, + })), + }, + message: + "You are not authorized to perform this action with the provided arguments.", + }); + } + } + } else { + const unauthorizedArgumentPaths = getKeyPathsWithNonUndefinedValues({ + keyPaths: [["input", "isPinned"]], + object: parsedArgs, + }); + + if (unauthorizedArgumentPaths.length !== 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_arguments", + issues: unauthorizedArgumentPaths.map((argumentPath) => ({ + argumentPath, + })), + }, + message: + "You are not authorized to perform this action with the provided arguments.", + }); + } + + if (currentUserId !== existingPost.creatorId) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + } + } + + const [updatedPost] = await ctx.drizzleClient + .update(postsTable) + .set({ + caption: parsedArgs.input.caption, + pinnedAt: + parsedArgs.input.isPinned === undefined + ? undefined + : parsedArgs.input.isPinned + ? existingPost.pinnedAt === null + ? new Date() + : undefined + : null, + updaterId: currentUserId, + }) + .where(eq(postsTable.id, parsedArgs.input.id)) + .returning(); + + // Updated post not being returned means that either it was already updated or its `id` column was changed by external entities before this update operation could take place. + if (updatedPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(updatedPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/updatePostVote.ts b/src/graphql/types/Mutation/updatePostVote.ts new file mode 100644 index 0000000000..b174e94cad --- /dev/null +++ b/src/graphql/types/Mutation/updatePostVote.ts @@ -0,0 +1,166 @@ +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { postVotesTable } from "~/src/drizzle/tables/postVotes"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdatePostVoteInput, + mutationUpdatePostVoteInputSchema, +} from "~/src/graphql/inputs/MutationUpdatePostVoteInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdatePostVoteArgumentsSchema = z.object({ + input: mutationUpdatePostVoteInputSchema, +}); + +builder.mutationField("updatePostVote", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdatePostVoteInput, + }), + }, + description: "Mutation field to update a post vote.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdatePostVoteArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost, existingPostVote] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + postAttachmentsWherePost: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.postId), + }), + ctx.drizzleClient.query.postVotesTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.creatorId, currentUserId), + operators.eq(fields.postId, parsedArgs.input.postId), + ), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined || existingPostVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "postId"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [updatedPostVote] = await ctx.drizzleClient + .update(postVotesTable) + .set({ + type: parsedArgs.input.type, + updaterId: currentUserId, + }) + .where( + and( + eq(postVotesTable.creatorId, currentUserId), + eq(postVotesTable.postId, parsedArgs.input.postId), + ), + ) + .returning(); + + // Updated post vote not being returned means that either it was deleted or its `creatorId` or `postId` columns were changed by external entities before this update operation. + if (updatedPostVote === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return Object.assign(existingPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Mutation/updateTag.ts b/src/graphql/types/Mutation/updateTag.ts new file mode 100644 index 0000000000..95b6a214f8 --- /dev/null +++ b/src/graphql/types/Mutation/updateTag.ts @@ -0,0 +1,247 @@ +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { tagsTable } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; +import { + MutationUpdateTagInput, + mutationUpdateTagInputSchema, +} from "~/src/graphql/inputs/MutationUpdateTagInput"; +import { Tag } from "~/src/graphql/types/Tag/Tag"; +import { isNotNullish } from "~/src/utilities/isNotNullish"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const mutationUpdateTagArgumentsSchema = z.object({ + input: mutationUpdateTagInputSchema, +}); + +builder.mutationField("updateTag", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: MutationUpdateTagInput, + }), + }, + description: "Mutation field to update a tag.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = mutationUpdateTagArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingTag] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.tagsTable.findFirst({ + columns: { + isFolder: true, + organizationId: true, + }, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + if (isNotNullish(parsedArgs.input.parentTagId)) { + const parentTagId = parsedArgs.input.parentTagId; + + const existingParentTag = + await ctx.drizzleClient.query.tagsTable.findFirst({ + columns: { + isFolder: true, + organizationId: true, + }, + where: (fields, operators) => operators.eq(fields.id, parentTagId), + }); + + if (existingParentTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "parentTagId"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + if (existingParentTag.organizationId !== existingTag.organizationId) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "parentTagId"], + message: + "This tag does not belong to the associated organization.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + + if (existingParentTag.isFolder !== true) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "parentTagId"], + message: "This must be a tag folder.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + } + + if (isNotNullish(parsedArgs.input.name)) { + const name = parsedArgs.input.name; + + const existingTagWithName = + await ctx.drizzleClient.query.tagsTable.findFirst({ + columns: {}, + where: (fields, operators) => + operators.and( + operators.eq(fields.isFolder, existingTag.isFolder), + operators.eq(fields.name, name), + operators.eq(fields.organizationId, existingTag.organizationId), + ), + }); + + if (existingTagWithName !== undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "name"], + message: "This name is not available.", + }, + ], + }, + message: + "This action is forbidden on the resources associated to the provided arguments.", + }); + } + } + + const currentUserOrganizationMembership = + existingTag.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + const [updatedTag] = await ctx.drizzleClient + .update(tagsTable) + .set({ + parentTagId: parsedArgs.input.parentTagId, + name: parsedArgs.input.name, + updaterId: currentUserId, + }) + .where(eq(tagsTable.id, parsedArgs.input.id)) + .returning(); + + // Updated tag not being returned means that either it was deleted or its `id` column was changed by external entities before this delete operation could take place. + if (updatedTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again.", + }); + } + + return updatedTag; + }, + type: Tag, + }), +); diff --git a/src/graphql/types/Mutation/updateUser.ts b/src/graphql/types/Mutation/updateUser.ts index 8ea5b2e530..3fdd563795 100755 --- a/src/graphql/types/Mutation/updateUser.ts +++ b/src/graphql/types/Mutation/updateUser.ts @@ -1,7 +1,7 @@ import { hash } from "@node-rs/argon2"; import { eq } from "drizzle-orm"; import { z } from "zod"; -import { usersTable } from "~/src/drizzle/schema"; +import { usersTable } from "~/src/drizzle/tables/users"; import { builder } from "~/src/graphql/builder"; import { MutationUpdateUserInput, diff --git a/src/graphql/types/Organization/Organization.ts b/src/graphql/types/Organization/Organization.ts new file mode 100644 index 0000000000..7ad0f2d106 --- /dev/null +++ b/src/graphql/types/Organization/Organization.ts @@ -0,0 +1,47 @@ +import type { organizationsTable } from "~/src/drizzle/tables/organizations"; +import { builder } from "~/src/graphql/builder"; +import { Iso3166Alpha2CountryCode } from "~/src/graphql/enums/Iso3166Alpha2CountryCode"; + +export type Organization = typeof organizationsTable.$inferSelect; + +export const Organization = builder.objectRef("Organization"); + +Organization.implement({ + description: "", + fields: (t) => ({ + address: t.exposeString("address", { + description: "Address of the organization.", + }), + avatarURI: t.exposeString("avatarURI", { + description: "URI to the avatar of the organization.", + }), + city: t.exposeString("city", { + description: "Name of the city where the organization exists in.", + }), + countryCode: t.expose("countryCode", { + description: + "Country code of the country the organization is a citizen of.", + type: Iso3166Alpha2CountryCode, + }), + createdAt: t.expose("createdAt", { + description: "Date time at the time the organization was created.", + type: "DateTime", + }), + description: t.exposeString("description", { + description: "Custom information about the organization.", + }), + id: t.exposeID("id", { + description: "Global identifier of the organization.", + nullable: false, + }), + name: t.exposeString("name", { + description: "Name of the organization.", + }), + postalCode: t.exposeString("postalCode", { + description: "Postal code of the organization.", + }), + state: t.exposeString("state", { + description: "Name of the state the organization exists in.", + }), + }), +}); diff --git a/src/graphql/types/Organization/advertisements.ts b/src/graphql/types/Organization/advertisements.ts new file mode 100644 index 0000000000..5a9327f002 --- /dev/null +++ b/src/graphql/types/Organization/advertisements.ts @@ -0,0 +1,225 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt } from "drizzle-orm"; +import type { z } from "zod"; +import { + advertisementsTable, + advertisementsTableInsertSchema, +} from "~/src/drizzle/tables/advertisements"; +import { Advertisement } from "~/src/graphql/types/Advertisement/Advertisement"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +const advertisementsArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = advertisementsTableInsertSchema.pick({ + name: true, +}); + +Organization.implement({ + fields: (t) => ({ + advertisements: t.connection( + { + description: + "GraphQL connection to traverse through the advertisements associated to the organization.", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = advertisementsArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = + await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [desc(advertisementsTable.name)] + : [asc(advertisementsTable.name)]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(advertisementsTable) + .where( + and( + eq(advertisementsTable.name, cursor.name), + eq(advertisementsTable.organizationId, parent.id), + ), + ), + ), + eq(advertisementsTable.organizationId, parent.id), + lt(advertisementsTable.name, cursor.name), + ); + } else { + where = eq(advertisementsTable.organizationId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(advertisementsTable) + .where( + and( + eq(advertisementsTable.name, cursor.name), + eq(advertisementsTable.organizationId, parent.id), + ), + ), + ), + eq(advertisementsTable.organizationId, parent.id), + gt(advertisementsTable.name, cursor.name), + ); + } else { + where = eq(advertisementsTable.organizationId, parent.id); + } + } + + const advertisements = + await ctx.drizzleClient.query.advertisementsTable.findMany({ + limit, + orderBy, + with: { + advertisementAttachmentsWhereAdvertisement: true, + }, + where, + }); + + if (cursor !== undefined && advertisements.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (advertisement) => + Buffer.from( + JSON.stringify({ + name: advertisement.name, + }), + ).toString("base64url"), + createNode: ({ + advertisementAttachmentsWhereAdvertisement, + ...advertisement + }) => + Object.assign(advertisement, { + attachments: advertisementAttachmentsWhereAdvertisement, + }), + parsedArgs, + rawNodes: advertisements, + }); + }, + type: Advertisement, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Organization/creator.ts b/src/graphql/types/Organization/creator.ts new file mode 100755 index 0000000000..ad33736225 --- /dev/null +++ b/src/graphql/types/Organization/creator.ts @@ -0,0 +1,48 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +Organization.implement({ + fields: (t) => ({ + creator: t.field({ + description: "User who created the organization.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (parent.creatorId === null) { + return null; + } + + const creatorId = parent.creatorId; + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); + + // Creator id existing but the associated user not existing is either a business logic error which means that the corresponding data in the database is in a corrupted state or it is a rare race condition. It must be investigated and fixed as soon as possible to prevent further data corruption if the former case is true. + if (existingUser === undefined) { + ctx.log.warn( + "Postgres select operation returned an empty array for a organization's creator id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Organization/index.ts b/src/graphql/types/Organization/index.ts new file mode 100644 index 0000000000..f60d9da80c --- /dev/null +++ b/src/graphql/types/Organization/index.ts @@ -0,0 +1,11 @@ +import "./Organization"; +import "./advertisements"; +import "./creator"; +import "./members"; +import "./pinnedPosts"; +import "./pinnedPostsCount"; +import "./posts"; +import "./postsCount"; +import "./tags"; +import "./updatedAt"; +import "./updater"; diff --git a/src/graphql/types/Organization/members.ts b/src/graphql/types/Organization/members.ts new file mode 100644 index 0000000000..eed9f782f3 --- /dev/null +++ b/src/graphql/types/Organization/members.ts @@ -0,0 +1,229 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt, or } from "drizzle-orm"; +import { z } from "zod"; +import { + organizationMembershipsTable, + organizationMembershipsTableInsertSchema, +} from "~/src/drizzle/tables/organizationMemberships"; +import { User } from "~/src/graphql/types/User/User"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +const membersArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = organizationMembershipsTableInsertSchema + .pick({ + memberId: true, + }) + .extend({ + createdAt: z.string().datetime(), + }) + .transform((arg) => ({ + createdAt: new Date(arg.createdAt), + memberId: arg.memberId, + })); + +Organization.implement({ + fields: (t) => ({ + members: t.connection( + { + description: + "Organization field to read the members of the organization by traversing through them using a graphql connection.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = membersArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [ + asc(organizationMembershipsTable.createdAt), + asc(organizationMembershipsTable.memberId), + asc(organizationMembershipsTable.organizationId), + ] + : [ + desc(organizationMembershipsTable.createdAt), + desc(organizationMembershipsTable.memberId), + desc(organizationMembershipsTable.organizationId), + ]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(organizationMembershipsTable) + .where( + and( + eq( + organizationMembershipsTable.memberId, + cursor.memberId, + ), + eq( + organizationMembershipsTable.organizationId, + parent.id, + ), + ), + ), + ), + eq(organizationMembershipsTable.organizationId, parent.id), + or( + and( + eq( + organizationMembershipsTable.createdAt, + cursor.createdAt, + ), + gt(organizationMembershipsTable.memberId, cursor.memberId), + ), + gt(organizationMembershipsTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = eq( + organizationMembershipsTable.organizationId, + parent.id, + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(organizationMembershipsTable) + .where( + and( + eq( + organizationMembershipsTable.memberId, + cursor.memberId, + ), + eq( + organizationMembershipsTable.organizationId, + parent.id, + ), + ), + ), + ), + eq(organizationMembershipsTable.organizationId, parent.id), + + or( + and( + eq( + organizationMembershipsTable.createdAt, + cursor.createdAt, + ), + lt(organizationMembershipsTable.memberId, cursor.memberId), + ), + lt(organizationMembershipsTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = eq( + organizationMembershipsTable.organizationId, + parent.id, + ); + } + } + + const organizationMemberships = + await ctx.drizzleClient.query.organizationMembershipsTable.findMany( + { + columns: { + createdAt: true, + memberId: true, + }, + limit, + orderBy, + with: { + member: true, + }, + where, + }, + ); + + if (cursor !== undefined && organizationMemberships.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (organizationMembership) => + Buffer.from( + JSON.stringify({ + createdAt: organizationMembership.createdAt, + memberId: organizationMembership.memberId, + }), + ).toString("base64url"), + createNode: (organizationMembership) => + organizationMembership.member, + parsedArgs, + rawNodes: organizationMemberships, + }); + }, + type: User, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Organization/pinnedPosts.ts b/src/graphql/types/Organization/pinnedPosts.ts new file mode 100644 index 0000000000..6490d3fe47 --- /dev/null +++ b/src/graphql/types/Organization/pinnedPosts.ts @@ -0,0 +1,256 @@ +import { + type SQL, + and, + asc, + desc, + eq, + exists, + gt, + lt, + ne, + or, + sql, +} from "drizzle-orm"; +import { z } from "zod"; +import { postsTable, postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +const pinnedPostsArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + id: postsTableInsertSchema.shape.id.unwrap(), + pinnedAt: postsTableInsertSchema.shape.pinnedAt.unwrap().unwrap(), +}); + +Organization.implement({ + fields: (t) => ({ + pinnedPosts: t.connection( + { + description: + "GraphQL connection to traverse through the pinned posts associated to the organization.", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = pinnedPostsArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = + await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(postsTable.pinnedAt), asc(postsTable.id)] + : [desc(postsTable.pinnedAt), desc(postsTable.id)]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postsTable) + .where( + and( + eq(postsTable.id, cursor.id), + eq(postsTable.organizationId, parent.id), + eq(postsTable.pinnedAt, cursor.pinnedAt), + ), + ), + ), + ne(postsTable.pinnedAt, sql`${null}`), + eq(postsTable.organizationId, parent.id), + or( + and( + eq(postsTable.pinnedAt, cursor.pinnedAt), + gt(postsTable.id, cursor.id), + ), + gt(postsTable.pinnedAt, cursor.pinnedAt), + ), + ); + } else { + where = and( + ne(postsTable.pinnedAt, sql`${null}`), + eq(postsTable.organizationId, parent.id), + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postsTable) + .where( + and( + eq(postsTable.id, cursor.id), + eq(postsTable.organizationId, parent.id), + eq(postsTable.pinnedAt, cursor.pinnedAt), + ), + ), + ), + ne(postsTable.pinnedAt, sql`${null}`), + eq(postsTable.organizationId, parent.id), + or( + and( + eq(postsTable.pinnedAt, cursor.pinnedAt), + lt(postsTable.id, cursor.id), + ), + lt(postsTable.pinnedAt, cursor.pinnedAt), + ), + ); + } else { + where = and( + ne(postsTable.pinnedAt, sql`${null}`), + eq(postsTable.organizationId, parent.id), + ); + } + } + + const pinnedPosts = await ctx.drizzleClient.query.postsTable.findMany( + { + limit, + orderBy, + with: { + postAttachmentsWherePost: true, + }, + where, + }, + ); + + if (cursor !== undefined && pinnedPosts.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (post) => + Buffer.from( + JSON.stringify({ + id: post.id, + pinnedAt: post.pinnedAt, + }), + ).toString("base64url"), + createNode: ({ postAttachmentsWherePost, ...post }) => + Object.assign(post, { + attachments: postAttachmentsWherePost, + }), + parsedArgs, + rawNodes: pinnedPosts, + }); + }, + type: Post, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Organization/pinnedPostsCount.ts b/src/graphql/types/Organization/pinnedPostsCount.ts new file mode 100644 index 0000000000..eec046be95 --- /dev/null +++ b/src/graphql/types/Organization/pinnedPostsCount.ts @@ -0,0 +1,31 @@ +import { and, count, eq, ne, sql } from "drizzle-orm"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { Organization } from "./Organization"; + +Organization.implement({ + fields: (t) => ({ + pinnedPostsCount: t.field({ + description: "Total number of pinned posts in the organization.", + resolve: async (parent, _args, ctx) => { + const [postsCount] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(postsTable) + .where( + and( + eq(postsTable.organizationId, parent.id), + ne(postsTable.pinnedAt, sql`${null}`), + ), + ); + + if (postsCount === undefined) { + return 0; + } + + return postsCount.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Organization/posts.ts b/src/graphql/types/Organization/posts.ts new file mode 100644 index 0000000000..23c5036f04 --- /dev/null +++ b/src/graphql/types/Organization/posts.ts @@ -0,0 +1,219 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt } from "drizzle-orm"; +import { z } from "zod"; +import { postsTable, postsTableInsertSchema } from "~/src/drizzle/tables/posts"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +const postsArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + id: postsTableInsertSchema.shape.id.unwrap(), +}); + +Organization.implement({ + fields: (t) => ({ + posts: t.connection( + { + description: + "GraphQL connection to traverse through the posts associated to the organization.", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = postsArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = + await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(postsTable.id)] + : [desc(postsTable.id)]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postsTable) + .where( + and( + eq(postsTable.id, cursor.id), + eq(postsTable.organizationId, parent.id), + ), + ), + ), + eq(postsTable.organizationId, parent.id), + gt(postsTable.id, cursor.id), + ); + } else { + where = eq(postsTable.organizationId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postsTable) + .where( + and( + eq(postsTable.id, cursor.id), + eq(postsTable.organizationId, parent.id), + ), + ), + ), + eq(postsTable.organizationId, parent.id), + lt(postsTable.id, cursor.id), + ); + } else { + where = eq(postsTable.organizationId, parent.id); + } + } + + const posts = await ctx.drizzleClient.query.postsTable.findMany({ + limit, + orderBy, + with: { + postAttachmentsWherePost: true, + }, + where, + }); + + if (cursor !== undefined && posts.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (post) => + Buffer.from( + JSON.stringify({ + id: post.id, + }), + ).toString("base64url"), + createNode: ({ postAttachmentsWherePost, ...post }) => + Object.assign(post, { + attachments: postAttachmentsWherePost, + }), + parsedArgs, + rawNodes: posts, + }); + }, + type: Post, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Organization/postsCount.ts b/src/graphql/types/Organization/postsCount.ts new file mode 100644 index 0000000000..a769e9837d --- /dev/null +++ b/src/graphql/types/Organization/postsCount.ts @@ -0,0 +1,26 @@ +import { count, eq } from "drizzle-orm"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { Organization } from "./Organization"; + +Organization.implement({ + fields: (t) => ({ + postsCount: t.field({ + description: "Total number of posts in the organization.", + resolve: async (parent, _args, ctx) => { + const [postsCount] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(postsTable) + .where(eq(postsTable.organizationId, parent.id)); + + if (postsCount === undefined) { + return 0; + } + + return postsCount.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Organization/tags.ts b/src/graphql/types/Organization/tags.ts new file mode 100644 index 0000000000..369d0ea122 --- /dev/null +++ b/src/graphql/types/Organization/tags.ts @@ -0,0 +1,228 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt, or } from "drizzle-orm"; +import type { z } from "zod"; +import { tagsTable, tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { Tag } from "~/src/graphql/types/Tag/Tag"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +const tagsArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = tagsTableInsertSchema.pick({ + isFolder: true, + name: true, +}); + +Organization.implement({ + fields: (t) => ({ + tags: t.connection( + { + description: + "GraphQL connection to traverse through the tags associated to the organization.", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = tagsArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = + await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(tagsTable.isFolder), asc(tagsTable.name)] + : [desc(tagsTable.isFolder), desc(tagsTable.name)]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(tagsTable) + .where( + and( + eq(tagsTable.isFolder, cursor.isFolder), + eq(tagsTable.name, cursor.name), + eq(tagsTable.organizationId, parent.id), + ), + ), + ), + eq(tagsTable.organizationId, parent.id), + or( + and( + eq(tagsTable.isFolder, cursor.isFolder), + gt(tagsTable.name, cursor.name), + ), + gt(tagsTable.isFolder, cursor.isFolder), + ), + ); + } else { + where = eq(tagsTable.organizationId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(tagsTable) + .where( + and( + eq(tagsTable.isFolder, cursor.isFolder), + eq(tagsTable.name, cursor.name), + eq(tagsTable.organizationId, parent.id), + ), + ), + ), + eq(tagsTable.organizationId, parent.id), + or( + and( + eq(tagsTable.isFolder, cursor.isFolder), + lt(tagsTable.name, cursor.name), + ), + lt(tagsTable.isFolder, cursor.isFolder), + ), + ); + } else { + where = eq(tagsTable.organizationId, parent.id); + } + } + + const tags = await ctx.drizzleClient.query.tagsTable.findMany({ + limit, + orderBy, + where, + }); + + if (cursor !== undefined && tags.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (tag) => + Buffer.from( + JSON.stringify({ + isFolder: tag.isFolder, + name: tag.name, + }), + ).toString("base64url"), + createNode: (tag) => tag, + parsedArgs, + rawNodes: tags, + }); + }, + type: Tag, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Organization/updatedAt.ts b/src/graphql/types/Organization/updatedAt.ts new file mode 100644 index 0000000000..c26e3052a0 --- /dev/null +++ b/src/graphql/types/Organization/updatedAt.ts @@ -0,0 +1,63 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +Organization.implement({ + fields: (t) => ({ + updatedAt: t.field({ + description: "Date time at the time the organization was last updated.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.updatedAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Organization/updater.ts b/src/graphql/types/Organization/updater.ts new file mode 100755 index 0000000000..7c42315946 --- /dev/null +++ b/src/graphql/types/Organization/updater.ts @@ -0,0 +1,94 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Organization } from "./Organization"; + +Organization.implement({ + fields: (t) => ({ + updater: t.field({ + description: "User who last updated the organization.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: + "Only authenticated organizations can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.id), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "forbidden_action", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.updaterId === null) { + return null; + } + + if (parent.updaterId === currentUserId) { + return currentUser; + } + + const updaterId = parent.updaterId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is either a business logic error which means that the corresponding data in the database is in a corrupted state or it is a rare race condition. It must be investigated and fixed as soon as possible to prevent further data corruption if the former case is true. + if (existingUser === undefined) { + ctx.log.warn( + "Postgres select operation returned an empty array for a organization's updater id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Post/Post.ts b/src/graphql/types/Post/Post.ts new file mode 100644 index 0000000000..f6f4f944be --- /dev/null +++ b/src/graphql/types/Post/Post.ts @@ -0,0 +1,41 @@ +import type { postsTable } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { + PostAttachment, + type PostAttachment as PostAttachmentType, +} from "~/src/graphql/types/PostAttachment/PostAttachment"; + +export type Post = typeof postsTable.$inferSelect & { + attachments: PostAttachmentType[] | null; +}; + +export const Post = builder.objectRef("Post"); + +Post.implement({ + description: "", + fields: (t) => ({ + attachments: t.expose("attachments", { + description: "Array of attachments.", + type: t.listRef(PostAttachment), + }), + caption: t.exposeString("caption", { + description: "Caption for the post.", + }), + createdAt: t.expose("createdAt", { + description: "Date time at the time the post was created.", + type: "DateTime", + }), + id: t.exposeID("id", { + description: "Global identifier of the post.", + nullable: false, + }), + pinnedAt: t.expose("pinnedAt", { + description: "Date time at the time the post was pinned.", + type: "DateTime", + }), + updatedAt: t.expose("updatedAt", { + description: "Date time at the time the post was last updated.", + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Post/comments.ts b/src/graphql/types/Post/comments.ts new file mode 100644 index 0000000000..23e1267f86 --- /dev/null +++ b/src/graphql/types/Post/comments.ts @@ -0,0 +1,176 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt } from "drizzle-orm"; +import { z } from "zod"; +import { + commentsTable, + commentsTableInsertSchema, +} from "~/src/drizzle/tables/comments"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +const commentsArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + id: commentsTableInsertSchema.shape.id.unwrap(), +}); + +Post.implement({ + fields: (t) => ({ + comments: t.connection( + { + description: + "GraphQL connection to traverse through the comments associated to the post.", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = commentsArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [desc(commentsTable.id)] + : [asc(commentsTable.id)]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentsTable) + .where( + and( + eq(commentsTable.id, cursor.id), + eq(commentsTable.postId, parent.id), + ), + ), + ), + eq(commentsTable.postId, parent.id), + lt(commentsTable.id, cursor.id), + ); + } else { + where = eq(commentsTable.postId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(commentsTable) + .where( + and( + eq(commentsTable.id, cursor.id), + eq(commentsTable.postId, parent.id), + ), + ), + ), + eq(commentsTable.postId, parent.id), + gt(commentsTable.id, cursor.id), + ); + } else { + where = eq(commentsTable.postId, parent.id); + } + } + + const comments = await ctx.drizzleClient.query.commentsTable.findMany( + { + limit, + orderBy, + where, + }, + ); + + if (cursor !== undefined && comments.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (comment) => + Buffer.from( + JSON.stringify({ + id: comment.id, + }), + ).toString("base64url"), + createNode: (comment) => comment, + parsedArgs, + rawNodes: comments, + }); + }, + type: Comment, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Post/commentsCount.ts b/src/graphql/types/Post/commentsCount.ts new file mode 100644 index 0000000000..58f56dc772 --- /dev/null +++ b/src/graphql/types/Post/commentsCount.ts @@ -0,0 +1,26 @@ +import { and, count, eq } from "drizzle-orm"; +import { commentsTable } from "~/src/drizzle/tables/comments"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + commentsCount: t.field({ + description: "Total number of comments on the post.", + resolve: async (parent, _args, ctx) => { + const [commentsCount] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(commentsTable) + .where(and(eq(commentsTable.postId, parent.id))); + + if (commentsCount === undefined) { + return 0; + } + + return commentsCount.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Post/creator.ts b/src/graphql/types/Post/creator.ts new file mode 100644 index 0000000000..bc1b814040 --- /dev/null +++ b/src/graphql/types/Post/creator.ts @@ -0,0 +1,40 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + creator: t.field({ + description: "User who created the post.", + resolve: async (parent, _args, ctx) => { + if (parent.creatorId === null) { + return null; + } + + const creatorId = parent.creatorId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); + + // Creator id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a post's creator id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Post/downVoters.ts b/src/graphql/types/Post/downVoters.ts new file mode 100644 index 0000000000..b60c38cff6 --- /dev/null +++ b/src/graphql/types/Post/downVoters.ts @@ -0,0 +1,219 @@ +import { + type SQL, + and, + asc, + desc, + eq, + exists, + gt, + lt, + ne, + or, + sql, +} from "drizzle-orm"; +import { z } from "zod"; +import { + postVotesTable, + postVotesTableInsertSchema, +} from "~/src/drizzle/tables/postVotes"; +import { User } from "~/src/graphql/types/User/User"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +const downVotersArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + createdAt: postVotesTableInsertSchema.shape.createdAt.unwrap(), + creatorId: postVotesTableInsertSchema.shape.creatorId.unwrap().unwrap(), +}); + +Post.implement({ + fields: (t) => ({ + downVoters: t.connection( + { + description: + "GraphQL connection to traverse through the voters that down voted the post.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = downVotersArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(postVotesTable.createdAt), asc(postVotesTable.creatorId)] + : [desc(postVotesTable.createdAt), desc(postVotesTable.creatorId)]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postVotesTable) + .where( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + eq(postVotesTable.creatorId, cursor.creatorId), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + ), + ), + ), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + or( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + gt(postVotesTable.creatorId, cursor.creatorId), + ), + gt(postVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(postVotesTable.creatorId, sql`${null}`), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postVotesTable) + .where( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + eq(postVotesTable.creatorId, cursor.creatorId), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + ), + ), + ), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + or( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + lt(postVotesTable.creatorId, cursor.creatorId), + ), + lt(postVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(postVotesTable.creatorId, sql`${null}`), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + ); + } + } + + const postVotes = + await ctx.drizzleClient.query.postVotesTable.findMany({ + columns: { + createdAt: true, + creatorId: true, + }, + limit, + orderBy, + with: { + creator: true, + }, + where, + }); + + if (cursor !== undefined && postVotes.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (vote) => + Buffer.from( + JSON.stringify({ + createdAt: vote.createdAt, + creatorId: vote.creatorId, + }), + ).toString("base64url"), + createNode: (vote) => vote.creator, + parsedArgs, + rawNodes: postVotes.filter( + ( + vote, + ): vote is typeof vote & { + creator: NonNullable<(typeof vote)["creator"]>; + } => vote.creator !== null, + ), + }); + }, + type: User, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Post/downVotesCount.ts b/src/graphql/types/Post/downVotesCount.ts new file mode 100644 index 0000000000..8f07b72de5 --- /dev/null +++ b/src/graphql/types/Post/downVotesCount.ts @@ -0,0 +1,31 @@ +import { and, count, eq } from "drizzle-orm"; +import { postVotesTable } from "~/src/drizzle/tables/postVotes"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + downVotesCount: t.field({ + description: "Total number of down votes on the post.", + resolve: async (parent, _args, ctx) => { + const [postVote] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(postVotesTable) + .where( + and( + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "down_vote"), + ), + ); + + if (postVote === undefined) { + return 0; + } + + return postVote.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Post/index.ts b/src/graphql/types/Post/index.ts new file mode 100644 index 0000000000..153cb584fb --- /dev/null +++ b/src/graphql/types/Post/index.ts @@ -0,0 +1,10 @@ +import "./Post"; +import "./comments"; +import "./commentsCount"; +import "./creator"; +import "./downVoters"; +import "./downVotesCount"; +import "./organization"; +import "./updater"; +import "./upVoters"; +import "./upVotesCount"; diff --git a/src/graphql/types/Post/organization.ts b/src/graphql/types/Post/organization.ts new file mode 100644 index 0000000000..8afd746239 --- /dev/null +++ b/src/graphql/types/Post/organization.ts @@ -0,0 +1,35 @@ +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + organization: t.field({ + description: "Organization which the post belongs to.", + resolve: async (parent, _args, ctx) => { + const existingOrganization = + await ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parent.organizationId), + }); + + // Organziation id existing but the associated organization not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingOrganization === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a post's organization id that isn't null.", + ); + + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), + }), +}); diff --git a/src/graphql/types/Post/upVoters.ts b/src/graphql/types/Post/upVoters.ts new file mode 100644 index 0000000000..b68d7aea6d --- /dev/null +++ b/src/graphql/types/Post/upVoters.ts @@ -0,0 +1,219 @@ +import { + type SQL, + and, + asc, + desc, + eq, + exists, + gt, + lt, + ne, + or, + sql, +} from "drizzle-orm"; +import { z } from "zod"; +import { + postVotesTable, + postVotesTableInsertSchema, +} from "~/src/drizzle/tables/postVotes"; +import { User } from "~/src/graphql/types/User/User"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +const upVotersArgumentsSchema = defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = z.object({ + createdAt: postVotesTableInsertSchema.shape.createdAt.unwrap(), + creatorId: postVotesTableInsertSchema.shape.creatorId.unwrap().unwrap(), +}); + +Post.implement({ + fields: (t) => ({ + upVoters: t.connection( + { + description: + "GraphQL connection to traverse through the voters that up voted the post.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = upVotersArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(postVotesTable.createdAt), asc(postVotesTable.creatorId)] + : [desc(postVotesTable.createdAt), desc(postVotesTable.creatorId)]; + + let where: SQL | undefined; + + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postVotesTable) + .where( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + eq(postVotesTable.creatorId, cursor.creatorId), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + ), + ), + ), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + or( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + gt(postVotesTable.creatorId, cursor.creatorId), + ), + gt(postVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(postVotesTable.creatorId, sql`${null}`), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + ); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(postVotesTable) + .where( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + eq(postVotesTable.creatorId, cursor.creatorId), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + ), + ), + ), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + or( + and( + eq(postVotesTable.createdAt, cursor.createdAt), + lt(postVotesTable.creatorId, cursor.creatorId), + ), + lt(postVotesTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = and( + ne(postVotesTable.creatorId, sql`${null}`), + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + ); + } + } + + const postVotes = + await ctx.drizzleClient.query.postVotesTable.findMany({ + columns: { + createdAt: true, + creatorId: true, + }, + limit, + orderBy, + with: { + creator: true, + }, + where, + }); + + if (cursor !== undefined && postVotes.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (vote) => + Buffer.from( + JSON.stringify({ + createdAt: vote.createdAt, + creatorId: vote.creatorId, + }), + ).toString("base64url"), + createNode: (vote) => vote.creator, + parsedArgs, + rawNodes: postVotes.filter( + ( + vote, + ): vote is typeof vote & { + creator: NonNullable<(typeof vote)["creator"]>; + } => vote.creator !== null, + ), + }); + }, + type: User, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Post/upVotesCount.ts b/src/graphql/types/Post/upVotesCount.ts new file mode 100644 index 0000000000..128b8c81cd --- /dev/null +++ b/src/graphql/types/Post/upVotesCount.ts @@ -0,0 +1,31 @@ +import { and, count, eq } from "drizzle-orm"; +import { postVotesTable } from "~/src/drizzle/tables/postVotes"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + upVotesCount: t.field({ + description: "Total number of up votes on the post.", + resolve: async (parent, _args, ctx) => { + const [postVote] = await ctx.drizzleClient + .select({ + count: count(), + }) + .from(postVotesTable) + .where( + and( + eq(postVotesTable.postId, parent.id), + eq(postVotesTable.type, "up_vote"), + ), + ); + + if (postVote === undefined) { + return 0; + } + + return postVote.count; + }, + type: "Int", + }), + }), +}); diff --git a/src/graphql/types/Post/updater.ts b/src/graphql/types/Post/updater.ts new file mode 100644 index 0000000000..03726679d7 --- /dev/null +++ b/src/graphql/types/Post/updater.ts @@ -0,0 +1,93 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Post } from "./Post"; + +Post.implement({ + fields: (t) => ({ + updater: t.field({ + description: "User who last updated the post.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.updaterId === null) { + return null; + } + + if (parent.updaterId === currentUserId) { + return currentUser; + } + + const updaterId = parent.updaterId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a post's updater id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/PostAttachment/PostAttachment.ts b/src/graphql/types/PostAttachment/PostAttachment.ts new file mode 100644 index 0000000000..70b098f3de --- /dev/null +++ b/src/graphql/types/PostAttachment/PostAttachment.ts @@ -0,0 +1,21 @@ +import type { postAttachmentsTable } from "~/src/drizzle/tables/postAttachments"; +import { builder } from "~/src/graphql/builder"; +import { PostAttachmentType } from "~/src/graphql/enums/PostAttachmentType"; + +export type PostAttachment = typeof postAttachmentsTable.$inferSelect; + +export const PostAttachment = + builder.objectRef("PostAttachment"); + +PostAttachment.implement({ + description: "", + fields: (t) => ({ + type: t.expose("type", { + description: "Type of the attachment.", + type: PostAttachmentType, + }), + uri: t.exposeString("uri", { + description: "URI to the attachment.", + }), + }), +}); diff --git a/src/graphql/types/PostAttachment/index.ts b/src/graphql/types/PostAttachment/index.ts new file mode 100644 index 0000000000..e29c1c5bf5 --- /dev/null +++ b/src/graphql/types/PostAttachment/index.ts @@ -0,0 +1 @@ +import "./PostAttachment"; diff --git a/src/graphql/types/Query/advertisement.ts b/src/graphql/types/Query/advertisement.ts new file mode 100644 index 0000000000..74190d41fb --- /dev/null +++ b/src/graphql/types/Query/advertisement.ts @@ -0,0 +1,133 @@ +import { z } from "zod"; +import { builder } from "~/src/graphql/builder"; +import { + QueryAdvertisementInput, + queryAdvertisementInputSchema, +} from "~/src/graphql/inputs/QueryAdvertisementInput"; +import { Advertisement } from "~/src/graphql/types/Advertisement/Advertisement"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const queryAdvertisementArgumentsSchema = z.object({ + input: queryAdvertisementInputSchema, +}); + +builder.queryField("advertisement", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: QueryAdvertisementInput, + }), + }, + description: "Query field to read an advertisement.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = queryAdvertisementArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const existingAdvertisement = + await ctx.drizzleClient.query.advertisementsTable.findFirst({ + with: { + advertisementAttachmentsWhereAdvertisement: true, + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }); + + if (existingAdvertisement === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingAdvertisement.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + return Object.assign(existingAdvertisement, { + attachments: + existingAdvertisement.advertisementAttachmentsWhereAdvertisement, + }); + }, + type: Advertisement, + }), +); diff --git a/src/graphql/types/Query/comment.ts b/src/graphql/types/Query/comment.ts new file mode 100644 index 0000000000..04c9935624 --- /dev/null +++ b/src/graphql/types/Query/comment.ts @@ -0,0 +1,134 @@ +import { z } from "zod"; +import { builder } from "~/src/graphql/builder"; +import { + QueryCommentInput, + queryCommentInputSchema, +} from "~/src/graphql/inputs/QueryCommentInput"; +import { Comment } from "~/src/graphql/types/Comment/Comment"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const queryCommentArgumentsSchema = z.object({ + input: queryCommentInputSchema, +}); + +builder.queryField("comment", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: QueryCommentInput, + }), + }, + description: "Query field to read a comment.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = queryCommentArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingComment] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.commentsTable.findFirst({ + with: { + post: { + columns: {}, + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingComment === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingComment.post.organization + .organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + return existingComment; + }, + type: Comment, + }), +); diff --git a/src/graphql/types/Query/index.ts b/src/graphql/types/Query/index.ts index 99edc70021..2e42c429cb 100644 --- a/src/graphql/types/Query/index.ts +++ b/src/graphql/types/Query/index.ts @@ -1,5 +1,10 @@ import "./Query"; +import "./advertisement"; +import "./comment"; import "./currentUser"; +import "./organization"; +import "./post"; import "./renewAuthenticationToken"; import "./signIn"; +import "./tag"; import "./user"; diff --git a/src/graphql/types/Query/organization.ts b/src/graphql/types/Query/organization.ts new file mode 100644 index 0000000000..6e2649487b --- /dev/null +++ b/src/graphql/types/Query/organization.ts @@ -0,0 +1,68 @@ +import { z } from "zod"; +import { builder } from "~/src/graphql/builder"; +import { + QueryOrganizationInput, + queryOrganizationInputSchema, +} from "~/src/graphql/inputs/QueryOrganizationInput"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const queryOrganizationArgumentsSchema = z.object({ + input: queryOrganizationInputSchema, +}); + +builder.queryField("organization", (t) => + t.field({ + args: { + input: t.arg({ + description: "Input required to read an organization.", + required: true, + type: QueryOrganizationInput, + }), + }, + description: "Query field to read an organization.", + resolve: async (_parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = queryOrganizationArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const organization = + await ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }); + + if (organization === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + return organization; + }, + type: Organization, + }), +); diff --git a/src/graphql/types/Query/post.ts b/src/graphql/types/Query/post.ts new file mode 100644 index 0000000000..c60fc7862b --- /dev/null +++ b/src/graphql/types/Query/post.ts @@ -0,0 +1,131 @@ +import { z } from "zod"; +import { builder } from "~/src/graphql/builder"; +import { + QueryPostInput, + queryPostInputSchema, +} from "~/src/graphql/inputs/QueryPostInput"; +import { Post } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const queryPostArgumentsSchema = z.object({ + input: queryPostInputSchema, +}); + +builder.queryField("post", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: QueryPostInput, + }), + }, + description: "Query field to read a post.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = queryPostArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const [currentUser, existingPost] = await Promise.all([ + ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }), + ctx.drizzleClient.query.postsTable.findFirst({ + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + postAttachmentsWherePost: true, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }), + ]); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if (existingPost === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingPost.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + return Object.assign(existingPost, { + attachments: existingPost.postAttachmentsWherePost, + }); + }, + type: Post, + }), +); diff --git a/src/graphql/types/Query/tag.ts b/src/graphql/types/Query/tag.ts new file mode 100644 index 0000000000..c7dc711614 --- /dev/null +++ b/src/graphql/types/Query/tag.ts @@ -0,0 +1,126 @@ +import { z } from "zod"; +import { builder } from "~/src/graphql/builder"; +import { + QueryTagInput, + queryTagInputSchema, +} from "~/src/graphql/inputs/QueryTagInput"; +import { Tag } from "~/src/graphql/types/Tag/Tag"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; + +const queryTagArgumentsSchema = z.object({ + input: queryTagInputSchema, +}); + +builder.queryField("tag", (t) => + t.field({ + args: { + input: t.arg({ + description: "", + required: true, + type: QueryTagInput, + }), + }, + description: "Query field to read a tag.", + resolve: async (_parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = queryTagArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const existingTag = await ctx.drizzleClient.query.tagsTable.findFirst({ + with: { + organization: { + columns: {}, + with: { + organizationMembershipsWhereOrganization: { + columns: {}, + where: (fields, operators) => + operators.eq(fields.memberId, currentUserId), + }, + }, + }, + }, + where: (fields, operators) => + operators.eq(fields.id, parsedArgs.input.id), + }); + + if (existingTag === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: "No associated resources found for the provided arguments.", + }); + } + + const currentUserOrganizationMembership = + existingTag.organization.organizationMembershipsWhereOrganization[0]; + + if ( + currentUser.role !== "administrator" && + currentUserOrganizationMembership === undefined + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action_on_arguments_associated_resources", + issues: [ + { + argumentPath: ["input", "id"], + }, + ], + }, + message: + "You are not authorized to perform this action on the resources associated to the provided arguments.", + }); + } + + return existingTag; + }, + type: Tag, + }), +); diff --git a/src/graphql/types/README.md b/src/graphql/types/README.md index be613e70dc..8cfcc8d938 100644 --- a/src/graphql/types/README.md +++ b/src/graphql/types/README.md @@ -46,7 +46,7 @@ import { builder } from "~/src/graphql/builder"; type Post = { body: string; - posterId: string; + creatorId: string; title: string; }; @@ -60,18 +60,18 @@ PostRef.implement({ }); ``` ```typescript -// ~/src/graphql/types/Post/poster.ts +// ~/src/graphql/types/Post/creator.ts import { builder } from "~/src/graphql/builder"; import { UserRef } from "~/src/graphql/types/User/User"; import { PostRef } from "./Post"; PostRef.implement({ fields: (t) => ({ - poster: t.expose({ + creator: t.expose({ resolve: (parent, args, ctx) => { return await ctx.drizzleClient.query.user.findFirst({ where: (fields, operators) => { - return operators.eq(fields.id, parent.posterId); + return operators.eq(fields.id, parent.creatorId); } }) }, @@ -83,7 +83,7 @@ PostRef.implement({ ```typescript // ~/src/graphql/types/Post/index.ts import "./Post"; -import "./poster"; +import "./creator"; ``` ```typescript // ~/src/graphql/types/index.ts diff --git a/src/graphql/types/Tag/Tag.ts b/src/graphql/types/Tag/Tag.ts new file mode 100644 index 0000000000..28bae8f566 --- /dev/null +++ b/src/graphql/types/Tag/Tag.ts @@ -0,0 +1,22 @@ +import type { tagsTable } from "~/src/drizzle/tables/tags"; +import { builder } from "~/src/graphql/builder"; + +export type Tag = typeof tagsTable.$inferSelect; + +export const Tag = builder.objectRef("Tag"); + +Tag.implement({ + description: "", + fields: (t) => ({ + id: t.exposeID("id", { + description: "Global identifier of the tag.", + nullable: false, + }), + isFolder: t.exposeBoolean("isFolder", { + description: "Boolean to tell if the tag is to be used as a tag folder.", + }), + name: t.exposeString("name", { + description: "Name of the tag.", + }), + }), +}); diff --git a/src/graphql/types/Tag/createdAt.ts b/src/graphql/types/Tag/createdAt.ts new file mode 100644 index 0000000000..0dce0cbf05 --- /dev/null +++ b/src/graphql/types/Tag/createdAt.ts @@ -0,0 +1,66 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + createdAt: t.field({ + description: "Date time at the time the tag was created.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.createdAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Tag/creator.ts b/src/graphql/types/Tag/creator.ts new file mode 100644 index 0000000000..3a6a1d9540 --- /dev/null +++ b/src/graphql/types/Tag/creator.ts @@ -0,0 +1,93 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + creator: t.field({ + description: "User who created the tag.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.creatorId === null) { + return null; + } + + if (parent.creatorId === currentUserId) { + return currentUser; + } + + const creatorId = parent.creatorId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); + + // Creator id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a user's creator id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/Tag/index.ts b/src/graphql/types/Tag/index.ts new file mode 100644 index 0000000000..a06067351f --- /dev/null +++ b/src/graphql/types/Tag/index.ts @@ -0,0 +1,8 @@ +import "./Tag"; +import "./createdAt"; +import "./creator"; +import "./organization"; +import "./parentTag"; +import "./tagsWhereParentTag"; +import "./updatedAt"; +import "./updater"; diff --git a/src/graphql/types/Tag/organization.ts b/src/graphql/types/Tag/organization.ts new file mode 100644 index 0000000000..d504966bb4 --- /dev/null +++ b/src/graphql/types/Tag/organization.ts @@ -0,0 +1,35 @@ +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + organization: t.field({ + description: "Organization which the tag belongs to.", + resolve: async (parent, _args, ctx) => { + const existingOrganization = + await ctx.drizzleClient.query.organizationsTable.findFirst({ + where: (fields, operators) => + operators.eq(fields.id, parent.organizationId), + }); + + // Organziation id existing but the associated organization not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingOrganization === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a tag's organization id that isn't null.", + ); + + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingOrganization; + }, + type: Organization, + }), + }), +}); diff --git a/src/graphql/types/Tag/parentTag.ts b/src/graphql/types/Tag/parentTag.ts new file mode 100644 index 0000000000..76b0116962 --- /dev/null +++ b/src/graphql/types/Tag/parentTag.ts @@ -0,0 +1,87 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + parentTagFolder: t.field({ + description: "Parent tag of the tag.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.parentTagId === null) { + return null; + } + + const parentTagId = parent.parentTagId; + + const existingTag = await ctx.drizzleClient.query.tagsTable.findFirst({ + where: (fields, operators) => operators.eq(fields.id, parentTagId), + }); + + // Parent tag id existing but the associated tag not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingTag === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a tag's parent tag id that isn't null.", + ); + + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingTag; + }, + type: Tag, + }), + }), +}); diff --git a/src/graphql/types/Tag/tagsWhereParentTag.ts b/src/graphql/types/Tag/tagsWhereParentTag.ts new file mode 100644 index 0000000000..897bd98fdc --- /dev/null +++ b/src/graphql/types/Tag/tagsWhereParentTag.ts @@ -0,0 +1,228 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt, or } from "drizzle-orm"; +import type { z } from "zod"; +import { tagsTable, tagsTableInsertSchema } from "~/src/drizzle/tables/tags"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +const tagsWhereParentTagArgumentsSchema = + defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = tagsTableInsertSchema.pick({ + isFolder: true, + name: true, +}); + +Tag.implement({ + fields: (t) => ({ + tagsWhereParentTag: t.connection( + { + description: + "GraphQL connection to traverse through the tags that have the tag as their parent tag", + resolve: async (parent, args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const { + data: parsedArgs, + error, + success, + } = tagsWhereParentTagArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = + await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => + operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [asc(tagsTable.isFolder), asc(tagsTable.name)] + : [desc(tagsTable.isFolder), desc(tagsTable.name)]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(tagsTable) + .where( + and( + eq(tagsTable.parentTagId, parent.id), + eq(tagsTable.isFolder, cursor.isFolder), + eq(tagsTable.name, cursor.name), + ), + ), + ), + eq(tagsTable.parentTagId, parent.id), + or( + and( + eq(tagsTable.isFolder, cursor.isFolder), + gt(tagsTable.name, cursor.name), + ), + gt(tagsTable.isFolder, cursor.isFolder), + ), + ); + } else { + where = eq(tagsTable.parentTagId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(tagsTable) + .where( + and( + eq(tagsTable.parentTagId, parent.id), + eq(tagsTable.isFolder, cursor.isFolder), + eq(tagsTable.name, cursor.name), + ), + ), + ), + eq(tagsTable.parentTagId, parent.id), + or( + and( + eq(tagsTable.isFolder, cursor.isFolder), + lt(tagsTable.name, cursor.name), + ), + lt(tagsTable.isFolder, cursor.isFolder), + ), + ); + } else { + where = eq(tagsTable.parentTagId, parent.id); + } + } + + const tags = await ctx.drizzleClient.query.tagsTable.findMany({ + limit, + orderBy, + where, + }); + + if (cursor !== undefined && tags.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (tag) => + Buffer.from( + JSON.stringify({ + isFolder: tag.isFolder, + name: tag.name, + }), + ).toString("base64url"), + createNode: (tag) => tag, + parsedArgs, + rawNodes: tags, + }); + }, + type: Tag, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/Tag/updatedAt.ts b/src/graphql/types/Tag/updatedAt.ts new file mode 100644 index 0000000000..f1e1d2ae12 --- /dev/null +++ b/src/graphql/types/Tag/updatedAt.ts @@ -0,0 +1,66 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + updatedAt: t.field({ + description: "Date time at the time the tag was last updated.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.updatedAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/Tag/updater.ts b/src/graphql/types/Tag/updater.ts new file mode 100644 index 0000000000..4a66b22b2d --- /dev/null +++ b/src/graphql/types/Tag/updater.ts @@ -0,0 +1,93 @@ +import { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { Tag } from "./Tag"; + +Tag.implement({ + fields: (t) => ({ + updater: t.field({ + description: "User who last updated the tag.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + with: { + organizationMembershipsWhereMember: { + columns: { + role: true, + }, + where: (fields, operators) => + operators.eq(fields.organizationId, parent.organizationId), + }, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserOrganizationMembership = + currentUser.organizationMembershipsWhereMember[0]; + + if ( + currentUser.role !== "administrator" && + (currentUserOrganizationMembership === undefined || + currentUserOrganizationMembership.role !== "administrator") + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + if (parent.updaterId === null) { + return null; + } + + if (parent.updaterId === currentUserId) { + return currentUser; + } + + const updaterId = parent.updaterId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { + ctx.log.error( + "Postgres select operation returned an empty array for a user's updater id that isn't null.", + ); + throw new TalawaGraphQLError({ + extensions: { + code: "unexpected", + }, + message: "Something went wrong. Please try again later.", + }); + } + + return existingUser; + }, + type: User, + }), + }), +}); diff --git a/src/graphql/types/User/User.ts b/src/graphql/types/User/User.ts index f9df8cd65a..b7408efbed 100644 --- a/src/graphql/types/User/User.ts +++ b/src/graphql/types/User/User.ts @@ -7,7 +7,7 @@ import { UserMaritalStatus } from "~/src/graphql/enums/UserMaritalStatus"; import { UserNatalSex } from "~/src/graphql/enums/UserNatalSex"; import { UserRole } from "~/src/graphql/enums/UserRole"; -export type User = Omit; +export type User = typeof usersTable.$inferSelect; export const User = builder.objectRef("User"); @@ -32,7 +32,7 @@ User.implement({ type: Iso3166Alpha2CountryCode, }), createdAt: t.expose("createdAt", { - description: "Datetime at the time the user was created.", + description: "Date time at the time the user was created.", type: "DateTime", }), description: t.exposeString("description", { @@ -89,10 +89,6 @@ User.implement({ state: t.exposeString("state", { description: "Name of the state the user resides in.", }), - updatedAt: t.expose("updatedAt", { - description: "Datetime at the time the user was last updated.", - type: "DateTime", - }), workPhoneNumber: t.expose("workPhoneNumber", { description: "The phone number to use to communicate with the user while they're at work.", diff --git a/src/graphql/types/User/creator.ts b/src/graphql/types/User/creator.ts index d5d049fee6..dfef305e59 100755 --- a/src/graphql/types/User/creator.ts +++ b/src/graphql/types/User/creator.ts @@ -4,7 +4,7 @@ import { User } from "./User"; User.implement({ fields: (t) => ({ creator: t.field({ - description: "User field to read the user who created the user.", + description: "User who created the user.", resolve: async (parent, _args, ctx) => { if (!ctx.currentClient.isAuthenticated) { throw new TalawaGraphQLError({ @@ -16,6 +16,7 @@ User.implement({ } const currentUserId = ctx.currentClient.user.id; + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ where: (fields, operators) => operators.eq(fields.id, currentUserId), }); @@ -41,19 +42,26 @@ User.implement({ }); } + if (parent.creatorId === null) { + return null; + } + if (parent.creatorId === currentUserId) { return currentUser; } - const creatorUser = await ctx.drizzleClient.query.usersTable.findFirst({ - where: (fields, operators) => - operators.eq(fields.id, parent.creatorId), - }); + const creatorId = parent.creatorId; + + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, creatorId), + }, + ); - // Creator user id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. - if (creatorUser === undefined) { + // Creator id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { ctx.log.error( - "Postgres select operation returned an empty array for a user's creator user id that isn't null.", + "Postgres select operation returned an empty array for a user's creator id that isn't null.", ); throw new TalawaGraphQLError({ extensions: { @@ -63,7 +71,7 @@ User.implement({ }); } - return creatorUser; + return existingUser; }, type: User, }), diff --git a/src/graphql/types/User/index.ts b/src/graphql/types/User/index.ts index 130c5f2830..498793159c 100644 --- a/src/graphql/types/User/index.ts +++ b/src/graphql/types/User/index.ts @@ -1,3 +1,5 @@ import "./User"; import "./creator"; +import "./organizationsWhereMember"; +import "./updatedAt"; import "./updater"; diff --git a/src/graphql/types/User/organizationsWhereMember.ts b/src/graphql/types/User/organizationsWhereMember.ts new file mode 100644 index 0000000000..5a5f6966bb --- /dev/null +++ b/src/graphql/types/User/organizationsWhereMember.ts @@ -0,0 +1,223 @@ +import { type SQL, and, asc, desc, eq, exists, gt, lt, or } from "drizzle-orm"; +import { z } from "zod"; +import { + organizationMembershipsTable, + organizationMembershipsTableInsertSchema, +} from "~/src/drizzle/tables/organizationMemberships"; +import { Organization } from "~/src/graphql/types/Organization/Organization"; +import { + defaultGraphQLConnectionArgumentsSchema, + transformDefaultGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "~/src/utilities/defaultGraphQLConnection"; +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { User } from "./User"; + +const organizationsMemberOfArgumentsSchema = + defaultGraphQLConnectionArgumentsSchema + .transform(transformDefaultGraphQLConnectionArguments) + .transform((arg, ctx) => { + let cursor: z.infer | undefined = undefined; + + try { + if (arg.cursor !== undefined) { + cursor = cursorSchema.parse( + JSON.parse(Buffer.from(arg.cursor, "base64url").toString("utf-8")), + ); + } + } catch (error) { + ctx.addIssue({ + code: "custom", + message: "Not a valid cursor.", + path: [arg.isInversed ? "before" : "after"], + }); + } + + return { + cursor, + isInversed: arg.isInversed, + limit: arg.limit, + }; + }); + +const cursorSchema = organizationMembershipsTableInsertSchema + .pick({ + organizationId: true, + }) + .extend({ + createdAt: z.string().datetime(), + }) + .transform((arg) => ({ + createdAt: new Date(arg.createdAt), + organizationId: arg.organizationId, + })); + +User.implement({ + fields: (t) => ({ + organizationsMemberOf: t.connection( + { + description: + "User field to read the organizations the user is a member of by traversing through them using a graphql connection.", + resolve: async (parent, args, ctx) => { + const { + data: parsedArgs, + error, + success, + } = organizationsMemberOfArgumentsSchema.safeParse(args); + + if (!success) { + throw new TalawaGraphQLError({ + extensions: { + code: "invalid_arguments", + issues: error.issues.map((issue) => ({ + argumentPath: issue.path, + message: issue.message, + })), + }, + message: "Invalid arguments provided.", + }); + } + + const { cursor, isInversed, limit } = parsedArgs; + + const orderBy = isInversed + ? [ + asc(organizationMembershipsTable.createdAt), + asc(organizationMembershipsTable.memberId), + asc(organizationMembershipsTable.organizationId), + ] + : [ + desc(organizationMembershipsTable.createdAt), + desc(organizationMembershipsTable.memberId), + desc(organizationMembershipsTable.organizationId), + ]; + + let where: SQL | undefined; + if (isInversed) { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(organizationMembershipsTable) + .where( + and( + eq(organizationMembershipsTable.memberId, parent.id), + eq( + organizationMembershipsTable.organizationId, + cursor.organizationId, + ), + ), + ), + ), + eq(organizationMembershipsTable.memberId, parent.id), + or( + and( + eq( + organizationMembershipsTable.createdAt, + cursor.createdAt, + ), + gt( + organizationMembershipsTable.organizationId, + cursor.organizationId, + ), + ), + gt(organizationMembershipsTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = eq(organizationMembershipsTable.memberId, parent.id); + } + } else { + if (cursor !== undefined) { + where = and( + exists( + ctx.drizzleClient + .select() + .from(organizationMembershipsTable) + .where( + and( + eq(organizationMembershipsTable.memberId, parent.id), + eq( + organizationMembershipsTable.memberId, + cursor.organizationId, + ), + ), + ), + ), + eq(organizationMembershipsTable.organizationId, parent.id), + or( + and( + eq( + organizationMembershipsTable.createdAt, + cursor.createdAt, + ), + lt( + organizationMembershipsTable.organizationId, + cursor.organizationId, + ), + ), + lt(organizationMembershipsTable.createdAt, cursor.createdAt), + ), + ); + } else { + where = eq(organizationMembershipsTable.memberId, parent.id); + } + } + + const organizationMemberships = + await ctx.drizzleClient.query.organizationMembershipsTable.findMany( + { + columns: { + createdAt: true, + organizationId: true, + }, + limit, + orderBy, + with: { + organization: true, + }, + where, + }, + ); + + if (cursor !== undefined && organizationMemberships.length === 0) { + throw new TalawaGraphQLError({ + extensions: { + code: "arguments_associated_resources_not_found", + issues: [ + { + argumentPath: [isInversed ? "before" : "after"], + }, + ], + }, + message: + "No associated resources found for the provided arguments.", + }); + } + + return transformToDefaultGraphQLConnection({ + createCursor: (organizationMembership) => + Buffer.from( + JSON.stringify({ + createdAt: organizationMembership.createdAt, + organizationId: organizationMembership.organizationId, + }), + ).toString("base64url"), + createNode: (organizationMembership) => + organizationMembership.organization, + parsedArgs, + rawNodes: organizationMemberships, + }); + }, + type: Organization, + }, + { + description: "", + }, + { + description: "", + }, + ), + }), +}); diff --git a/src/graphql/types/User/updatedAt.ts b/src/graphql/types/User/updatedAt.ts new file mode 100644 index 0000000000..c2b309013c --- /dev/null +++ b/src/graphql/types/User/updatedAt.ts @@ -0,0 +1,53 @@ +import { TalawaGraphQLError } from "~/src/utilities/talawaGraphQLError"; +import { User } from "./User"; + +User.implement({ + fields: (t) => ({ + updatedAt: t.field({ + description: "Date time at the time the user was last updated.", + resolve: async (parent, _args, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + const currentUserId = ctx.currentClient.user.id; + + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ + columns: { + role: true, + }, + where: (fields, operators) => operators.eq(fields.id, currentUserId), + }); + + if (currentUser === undefined) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthenticated", + }, + message: "Only authenticated users can perform this action.", + }); + } + + if ( + currentUser.role !== "administrator" && + currentUserId !== parent.id + ) { + throw new TalawaGraphQLError({ + extensions: { + code: "unauthorized_action", + }, + message: "You are not authorized to perform this action.", + }); + } + + return parent.updatedAt; + }, + type: "DateTime", + }), + }), +}); diff --git a/src/graphql/types/User/updater.ts b/src/graphql/types/User/updater.ts index 3755a53887..2440c227ba 100755 --- a/src/graphql/types/User/updater.ts +++ b/src/graphql/types/User/updater.ts @@ -4,7 +4,7 @@ import { User } from "./User"; User.implement({ fields: (t) => ({ updater: t.field({ - description: "User field to read the user who last updated the user.", + description: "User who last updated the user.", resolve: async (parent, _args, ctx) => { if (!ctx.currentClient.isAuthenticated) { throw new TalawaGraphQLError({ @@ -16,6 +16,7 @@ User.implement({ } const currentUserId = ctx.currentClient.user.id; + const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({ where: (fields, operators) => operators.eq(fields.id, currentUserId), }); @@ -50,14 +51,17 @@ User.implement({ } const updaterId = parent.updaterId; - const updaterUser = await ctx.drizzleClient.query.usersTable.findFirst({ - where: (fields, operators) => operators.eq(fields.id, updaterId), - }); - // Updater user id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. - if (updaterUser === undefined) { + const existingUser = await ctx.drizzleClient.query.usersTable.findFirst( + { + where: (fields, operators) => operators.eq(fields.id, updaterId), + }, + ); + + // Updater id existing but the associated user not existing is a business logic error and means that the corresponding data in the database is in a corrupted state. It must be investigated and fixed as soon as possible to prevent additional data corruption. + if (existingUser === undefined) { ctx.log.error( - "Postgres select operation returned an empty array for a user's updater user id that isn't null.", + "Postgres select operation returned an empty array for a user's updater id that isn't null.", ); throw new TalawaGraphQLError({ extensions: { @@ -67,7 +71,7 @@ User.implement({ }); } - return updaterUser; + return existingUser; }, type: User, }), diff --git a/src/graphql/types/index.ts b/src/graphql/types/index.ts index 5098b9a299..2c6253df19 100644 --- a/src/graphql/types/index.ts +++ b/src/graphql/types/index.ts @@ -1,5 +1,12 @@ +import "./Advertisement/index"; +import "./AdvertisementAttachment/index"; import "./AuthenticationPayload"; +import "./Comment/index"; import "./Mutation/index"; +import "./Organization/index"; +import "./Post/index"; +import "./PostAttachment/index"; import "./Query/index"; import "./Subscription/index"; +import "./Tag/index"; import "./User/index"; diff --git a/src/utilities/defaultGraphQLConnection.ts b/src/utilities/defaultGraphQLConnection.ts new file mode 100755 index 0000000000..e68d877f44 --- /dev/null +++ b/src/utilities/defaultGraphQLConnection.ts @@ -0,0 +1,256 @@ +import { z } from "zod"; + +/** + * Type of the object containing the parsed default arguments of a graphql connection. + */ +export type ParsedDefaultGraphQLConnectionArguments = { + /** + * + */ + cursor?: Cursor | undefined; + /** + * The amount of graphql connection edges to return in a single graphql connection operation. + */ + limit: number; + /** + * This field is used to identify whether the client wants to traverse the graphql connection edges in the default order or in the inversed order. + * + * @example + * An example would be scrolling on twitter's home page(assuming they're using graphql connections for fetching array-like data). When scrolling down, the graphql connection traversal is the default and when scrolling up, the graphql connection traversal is inversed. + */ + isInversed: boolean; +}; + +/** + * Zod schema to parse the default graphql connection arguments and transform them to make them easier to work with. + */ +export const defaultGraphQLConnectionArgumentsSchema = z.object({ + after: z + .string() + .nullish() + .transform((arg) => (arg === null ? undefined : arg)), + before: z + .string() + .nullish() + .transform((arg) => (arg === null ? undefined : arg)), + first: z + .number() + .min(1) + .max(32) + .nullish() + .transform((arg) => (arg === null ? undefined : arg)), + last: z + .number() + .min(1) + .max(32) + .nullish() + .transform((arg) => (arg === null ? undefined : arg)), +}); + +export const transformDefaultGraphQLConnectionArguments = < + Arg extends z.infer, +>( + arg: Arg, + ctx: z.RefinementCtx, +) => { + const transformedArg: ParsedDefaultGraphQLConnectionArguments = { + cursor: undefined, + isInversed: false, + limit: 0, + }; + + const { after, before, first, last, ...customArg } = arg; + + if (first !== undefined) { + if (last !== undefined) { + ctx.addIssue({ + code: "custom", + message: `Argument "last" cannot be provided with argument "first".`, + path: ["last"], + }); + } + + if (before !== undefined) { + ctx.addIssue({ + code: "custom", + message: `Argument "before" cannot be provided with argument "first".`, + path: ["before"], + }); + } + + transformedArg.isInversed = false; + // The limit is increased by 1 to check for the existence of next connection edge by fetching one additional raw node in the connection resolver and providing this information in the field `hasNextPage` of the connection object's `pageInfo` field. + transformedArg.limit = first + 1; + + if (after !== undefined) { + transformedArg.cursor = after; + } + } else if (last !== undefined) { + if (after !== undefined) { + ctx.addIssue({ + code: "custom", + message: `Argument "after" cannot be provided with argument "last".`, + path: ["after"], + }); + } + + transformedArg.isInversed = true; + // The limit is increased by 1 to check for the existence of previous connection edge by fetching one additional raw node in the connection resolver and providing this information in the field `hasPreviousPage` of the connection object's `pageInfo` field. + transformedArg.limit = last + 1; + + if (before !== undefined) { + transformedArg.cursor = before; + } + } else { + ctx.addIssue({ + code: "custom", + message: `A non-null value for argument "first" must be provided.`, + path: ["first"], + }); + ctx.addIssue({ + code: "custom", + message: `A non-null value for argument "last" must be provided.`, + path: ["last"], + }); + } + + return { + ...transformedArg, + ...customArg, + }; +}; + +/** + * This is typescript type of a base graphql connection edge object. This connection edge object can be extended to create a custom connection edge object as long as the new connection edge object adheres to the default type of this base connection edge object. + */ +export type DefaultGraphQLConnectionEdge = { + cursor: string; + node: NodeType; +}; + +/** + * This is typescript type of a base graphql connection page info object. This connection page info object can be extended to create a custom connnection page info object as long as the new connection object adheres to the default type of this base connection object. + */ +export type DefaultGraphQLConnectionPageInfo = { + endCursor: string | null; + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor: string | null; +}; + +/** + * This is typescript type of a base graphql connection object. This connection object can be extended to create a custom connnection object as long as the new connection object adheres to the default type of this base connection object. + */ +export type DefaultGraphQLConnection = { + edges: DefaultGraphQLConnectionEdge[]; + pageInfo: DefaultGraphQLConnectionPageInfo; +}; + +/** + * This function is used to transform an array of objects to a standard graphql connection object. + * + * @remarks + * The logic used in this function is common to almost all graphql connection creation flows, abstracting that away into this function lets developers use a declarative way to create the graphql connection object they want and prevent code duplication. + * + * @example + * + * const orderBy = parsedArgs.isInverted ? asc(fields.id) : desc(fields.id); + * let where; + * + * if (parsedArgs.isInverted) { + * if (parsedArgs.cursor !== undefined) { + * where = and(eq(usersTable.id, parsedArgs.cursor), lt(usersTable.id, parsedArgs.cursor)); + * } + * } else { + * if (parsedArgs.cursor !== undefined) { + * where = and(eq(usersTable.id, parsedArgs.cursor), gt(usersTable.id, parsedArgs.cursor)); + * } + * } + * + * const users = await drizzleClient.usersTable.findMany({ + * limit: parsedArgs.limit, + * orderBy, + * where, + * }) + * + * const usersConnection = transformToDefaultGraphQLConnection({ + * createCursor: (rawNode) => rawNode.id, + * createNode: (rawNode) => rawNode, + * parsedArgs, + * rawNodes: users, + * }); + */ +export const transformToDefaultGraphQLConnection = < + RawNode, + Node = RawNode, + Cursor = string, +>({ + createCursor, + createNode, + parsedArgs: { cursor, isInversed, limit }, + rawNodes, +}: { + createCursor: (rawNode: RawNode) => string; + createNode: (rawNode: RawNode) => Node; + parsedArgs: ParsedDefaultGraphQLConnectionArguments; + rawNodes: RawNode[]; +}): DefaultGraphQLConnection => { + const connection: DefaultGraphQLConnection = { + edges: [], + pageInfo: { + endCursor: null, + hasNextPage: false, + hasPreviousPage: false, + startCursor: null, + }, + }; + + // If the arguments `before` and `last` are used. + if (isInversed) { + if (rawNodes.length === limit) { + connection.pageInfo.hasPreviousPage = true; + // Remove the extra fetched node. + rawNodes.shift(); + } else { + connection.pageInfo.hasPreviousPage = false; + } + + // If the cursor is `undefined` it means that the connection is at the very beginning and there are no edges after it. + connection.pageInfo.hasNextPage = cursor !== undefined; + + for (const rawNode of rawNodes.reverse()) { + connection.edges.push({ + cursor: createCursor(rawNode), + node: createNode(rawNode), + }); + } + } + // If the arguments `after` and `first` are used. + else { + if (rawNodes.length === limit) { + connection.pageInfo.hasNextPage = true; + // Remove the extra fetched node. + rawNodes.pop(); + } else { + connection.pageInfo.hasNextPage = false; + } + + // If the cursor is `undefined` it means that the connection is at the very beginning and there are no edges before it. + connection.pageInfo.hasPreviousPage = cursor !== undefined; + + for (const rawNode of rawNodes) { + connection.edges.push({ + cursor: createCursor(rawNode), + node: createNode(rawNode), + }); + } + } + + const endCursor = connection.edges[0]?.cursor; + const startCursor = connection.edges[connection.edges.length - 1]?.cursor; + connection.pageInfo.endCursor = endCursor !== undefined ? endCursor : null; + connection.pageInfo.startCursor = + startCursor !== undefined ? startCursor : null; + + return connection; +}; diff --git a/src/utilities/getKeyPathsWithNonUndefinedValues.ts b/src/utilities/getKeyPathsWithNonUndefinedValues.ts new file mode 100644 index 0000000000..f6469249f3 --- /dev/null +++ b/src/utilities/getKeyPathsWithNonUndefinedValues.ts @@ -0,0 +1,33 @@ +type Paths = T extends object + ? { [K in keyof T]: [K, ...Paths] | [K] }[keyof T] + : never; + +/** + * This function takes in a javascript object and a list of key paths within that object as arguments and outputs all paths amongst those key paths that correspond to a non-undefined value. + */ +export const getKeyPathsWithNonUndefinedValues = < + T extends Record, +>({ + keyPaths, + object, +}: { + keyPaths: Paths[]; + object: T; +}): Paths[] => { + const keyPathsWithNonUndefinedValues: Paths[] = []; + + for (const keyPath of keyPaths) { + // biome-ignore lint/suspicious/noExplicitAny: + const value = keyPath.reduce((accumulator: any, key) => { + return accumulator && accumulator[key] !== undefined + ? accumulator[key] + : undefined; + }, object); + + if (value !== undefined) { + keyPathsWithNonUndefinedValues.push(keyPath); + } + } + + return keyPathsWithNonUndefinedValues; +}; diff --git a/src/utilities/isNotNullish.ts b/src/utilities/isNotNullish.ts new file mode 100644 index 0000000000..cd2487abcb --- /dev/null +++ b/src/utilities/isNotNullish.ts @@ -0,0 +1,15 @@ +/** + * This function is used to check nullish state of a value passed to it. Nullish means the value being either `null` or `undefined`. If the value is found to be nullish, the function returns the boolean `false`, else it returns the boolean `true`. + * @example + * Here's an example:- + * function print(str: string | null) \{ + * if(isNotNullish(str)) \{ + * console.log(`the string is ${str}`) + * \} else \{ + * console.log(`the string is null`) + * \} + * \} + */ +export function isNotNullish(value: T0 | undefined | null): value is T0 { + return value !== undefined && value !== null; +} diff --git a/src/utilities/talawaGraphQLError.ts b/src/utilities/talawaGraphQLError.ts index 034daedb6a..749d0b0a79 100644 --- a/src/utilities/talawaGraphQLError.ts +++ b/src/utilities/talawaGraphQLError.ts @@ -163,6 +163,30 @@ export type UnauthorizedActionOnArgumentsAssociatedResourcesExtensions = { code: "unauthorized_action_on_arguments_associated_resources"; }; +/** + * When the client is not authorized to perform an action with certain arguments. + * + * @example + * throw new TalawaGraphQLError({ + * extensions: { + * code: "unauthorized_arguments", + * issues: [ + * { + * argumentPath: ["input", "role"], + * message: "You are not authorzied to change your user role.", + * }, + * ], + * }, + * message: "You are not authorized to perform this action with the provided arguments." + * }) + */ +export type UnauthorizedArgumentsExtensions = { + issues: { + argumentPath: (string | number)[]; + }[]; + code: "unauthorized_arguments"; +}; + /** * When an error that doesn't fit one of the error types listed above occurs. One example would be a database request failure. * @@ -186,6 +210,7 @@ export type TalawaGraphQLErrorExtensions = | InvalidArgumentsExtensions | UnauthorizedActionExtensions | UnauthorizedActionOnArgumentsAssociatedResourcesExtensions + | UnauthorizedArgumentsExtensions | UnexpectedExtensions; /** @@ -210,7 +235,7 @@ export type TalawaGraphQLErrorExtensions = * }, * ], * }, - * message: "Not associated resources found for the provided arguments.", + * message: "No associated resources found for the provided arguments.", * }) * } * diff --git a/test/routes/graphql/Mutation/createUser.test.ts b/test/routes/graphql/Mutation/createUser.test.ts index 458024b59a..e943100cae 100644 --- a/test/routes/graphql/Mutation/createUser.test.ts +++ b/test/routes/graphql/Mutation/createUser.test.ts @@ -31,7 +31,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -81,7 +81,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -111,7 +111,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -178,7 +178,7 @@ suite("Mutation field createUser", () => { name: "", password: "", postalCode: "", - role: "base", + role: "regular", state: "", }, }, @@ -236,7 +236,6 @@ suite("Mutation field createUser", () => { }); test(`length of the value of the argument "input.address" is more than 1025. - length of the value of the argument "input.avatarURI" is more than 2048. length of the value of the argument "input.city" is more than 64. length of the value of the argument "input.description" is more than 2048. length of the value of the argument "input.name" is more than 256. @@ -269,7 +268,6 @@ suite("Mutation field createUser", () => { variables: { input: { address: `address${faker.string.alpha(1025)}`, - avatarURI: `avatarURI${faker.string.alpha(2049)}`, city: `city${faker.string.alpha(65)}`, description: `description${faker.string.alpha(2049)}`, emailAddress: `emailAddress${faker.string.nanoid()}@email.com`, @@ -277,7 +275,7 @@ suite("Mutation field createUser", () => { name: `name${faker.string.alpha(257)}`, password: `password${faker.string.alpha(65)}`, postalCode: `postalCode${faker.string.alpha(33)}`, - role: "base", + role: "regular", state: `state${faker.string.alpha(65)}`, }, }, @@ -297,10 +295,6 @@ suite("Mutation field createUser", () => { argumentPath: ["input", "address"], message: expect.any(String), }, - { - argumentPath: ["input", "avatarURI"], - message: expect.any(String), - }, { argumentPath: ["input", "city"], message: expect.any(String), @@ -369,7 +363,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -391,7 +385,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -449,7 +443,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -522,7 +516,7 @@ suite("Mutation field createUser", () => { mobilePhoneNumber: "+11111111", name: "name", password: "password", - role: "base", + role: "regular", natalSex: "female", postalCode: "postalCode", state: "state", @@ -574,7 +568,6 @@ suite("Mutation field createUser", () => { role: variables.input.role, state: variables.input.state, workPhoneNumber: variables.input.workPhoneNumber, - updatedAt: expect.any(String), }), }), ); @@ -615,7 +608,7 @@ suite("Mutation field createUser", () => { mobilePhoneNumber: null, name: "name", password: "password", - role: "base", + role: "regular", natalSex: null, postalCode: null, state: null, @@ -690,7 +683,7 @@ suite("Mutation field createUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }; diff --git a/test/routes/graphql/Mutation/deleteCurrentUser.test.ts b/test/routes/graphql/Mutation/deleteCurrentUser.test.ts index 73e61309d6..bdfa523693 100644 --- a/test/routes/graphql/Mutation/deleteCurrentUser.test.ts +++ b/test/routes/graphql/Mutation/deleteCurrentUser.test.ts @@ -67,7 +67,7 @@ suite("Mutation field deleteCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -142,7 +142,7 @@ suite("Mutation field deleteCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }); diff --git a/test/routes/graphql/Mutation/deleteUser.test.ts b/test/routes/graphql/Mutation/deleteUser.test.ts index 6ba22851b1..c2f4096a74 100644 --- a/test/routes/graphql/Mutation/deleteUser.test.ts +++ b/test/routes/graphql/Mutation/deleteUser.test.ts @@ -51,7 +51,7 @@ suite("Mutation field deleteUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -114,7 +114,7 @@ suite("Mutation field deleteUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -259,7 +259,7 @@ suite("Mutation field deleteUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -421,7 +421,7 @@ suite("Mutation field deleteUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -507,7 +507,7 @@ suite("Mutation field deleteUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }); diff --git a/test/routes/graphql/Mutation/signUp.test.ts b/test/routes/graphql/Mutation/signUp.test.ts index 35d6d3afd4..aa56752914 100644 --- a/test/routes/graphql/Mutation/signUp.test.ts +++ b/test/routes/graphql/Mutation/signUp.test.ts @@ -148,7 +148,6 @@ suite("Mutation field signUp", () => { }); test(`length of the value of the argument "input.address" is more than 1025. - length of the value of the argument "input.avatarURI" is more than 2048. length of the value of the argument "input.city" is more than 64. length of the value of the argument "input.confirmedPassword" is more than 64. length of the value of the argument "input.description" is more than 2048. @@ -160,7 +159,6 @@ suite("Mutation field signUp", () => { variables: { input: { address: `address${faker.string.alpha(1025)}`, - avatarURI: `avatarURI${faker.string.alpha(2049)}`, city: `city${faker.string.alpha(65)}`, confirmedPassword: `confirmedPassword${faker.string.alpha(65)}`, description: `description${faker.string.alpha(2049)}`, @@ -186,10 +184,6 @@ suite("Mutation field signUp", () => { argumentPath: ["input", "address"], message: expect.any(String), }, - { - argumentPath: ["input", "avatarURI"], - message: expect.any(String), - }, { argumentPath: ["input", "city"], message: expect.any(String), @@ -372,10 +366,9 @@ suite("Mutation field signUp", () => { name: variables.input.name, natalSex: variables.input.natalSex, postalCode: variables.input.postalCode, - role: "base", + role: "regular", state: variables.input.state, workPhoneNumber: variables.input.workPhoneNumber, - updatedAt: expect.any(String), }), }), ); diff --git a/test/routes/graphql/Mutation/updateCurrentUser.test.ts b/test/routes/graphql/Mutation/updateCurrentUser.test.ts index d97e8e9d0a..3296fb51b5 100644 --- a/test/routes/graphql/Mutation/updateCurrentUser.test.ts +++ b/test/routes/graphql/Mutation/updateCurrentUser.test.ts @@ -75,7 +75,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -161,7 +161,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -244,7 +244,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -326,7 +326,6 @@ suite("Mutation field updateCurrentUser", () => { }); test(`length of the value of the argument "input.address" is more than 1025. - length of the value of the argument "input.avatarURI" is more than 2048. length of the value of the argument "input.city" is more than 64. length of the value of the argument "input.description" is more than 2048. length of the value of the argument "input.name" is more than 256. @@ -362,7 +361,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -381,7 +380,6 @@ suite("Mutation field updateCurrentUser", () => { variables: { input: { address: `address${faker.string.alpha(1025)}`, - avatarURI: `avatarURI${faker.string.alpha(2049)}`, city: `city${faker.string.alpha(65)}`, description: `description${faker.string.alpha(2049)}`, name: `name${faker.string.alpha(257)}`, @@ -406,10 +404,6 @@ suite("Mutation field updateCurrentUser", () => { argumentPath: ["input", "address"], message: expect.any(String), }, - { - argumentPath: ["input", "avatarURI"], - message: expect.any(String), - }, { argumentPath: ["input", "city"], message: expect.any(String), @@ -475,7 +469,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -566,7 +560,7 @@ suite("Mutation field updateCurrentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -659,7 +653,7 @@ suite("Mutation field updateCurrentUser", () => { natalSex: "male", password: "password", postalCode: "postal code", - role: "base", + role: "regular", state: "state", workPhoneNumber: "+11111111", }, @@ -740,10 +734,9 @@ suite("Mutation field updateCurrentUser", () => { name: updateCurrentUserVariables.input.name, natalSex: updateCurrentUserVariables.input.natalSex, postalCode: updateCurrentUserVariables.input.postalCode, - role: "base", + role: "regular", state: updateCurrentUserVariables.input.state, workPhoneNumber: updateCurrentUserVariables.input.workPhoneNumber, - updatedAt: expect.any(String), }), ); }); diff --git a/test/routes/graphql/Mutation/updateUser.test.ts b/test/routes/graphql/Mutation/updateUser.test.ts index 896063459a..69de065f81 100644 --- a/test/routes/graphql/Mutation/updateUser.test.ts +++ b/test/routes/graphql/Mutation/updateUser.test.ts @@ -52,7 +52,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -115,7 +115,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -202,7 +202,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -285,7 +285,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -366,7 +366,6 @@ suite("Mutation field updateUser", () => { }); test(`length of the value of the argument "input.address" is more than 1025. - length of the value of the argument "input.avatarURI" is more than 2048. length of the value of the argument "input.city" is more than 64. length of the value of the argument "input.description" is more than 2048. length of the value of the argument "input.name" is more than 256. @@ -402,7 +401,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -419,7 +418,6 @@ suite("Mutation field updateUser", () => { variables: { input: { address: `address${faker.string.alpha(1025)}`, - avatarURI: `avatarURI${faker.string.alpha(2049)}`, city: `city${faker.string.alpha(65)}`, description: `description${faker.string.alpha(2049)}`, id: createUserResult.data.createUser.user.id, @@ -445,10 +443,6 @@ suite("Mutation field updateUser", () => { argumentPath: ["input", "address"], message: expect.any(String), }, - { - argumentPath: ["input", "avatarURI"], - message: expect.any(String), - }, { argumentPath: ["input", "city"], message: expect.any(String), @@ -516,7 +510,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -616,7 +610,7 @@ suite("Mutation field updateUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -768,7 +762,7 @@ suite("Mutation field updateUser", () => { natalSex: "male", password: "password", postalCode: "postal code", - role: "base", + role: "regular", state: "state", workPhoneNumber: "+11111111", }, @@ -852,7 +846,6 @@ suite("Mutation field updateUser", () => { role: updateUserVariables.input.role, state: updateUserVariables.input.state, workPhoneNumber: updateUserVariables.input.workPhoneNumber, - updatedAt: expect.any(String), }), ); }); diff --git a/test/routes/graphql/Query/currentUser.test.ts b/test/routes/graphql/Query/currentUser.test.ts index c2164e00ff..7a49182e66 100644 --- a/test/routes/graphql/Query/currentUser.test.ts +++ b/test/routes/graphql/Query/currentUser.test.ts @@ -66,7 +66,7 @@ suite("Query field currentUser", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, diff --git a/test/routes/graphql/Query/renewAuthenticationToken.test.ts b/test/routes/graphql/Query/renewAuthenticationToken.test.ts index f2244ee4f1..c8bc35c28e 100644 --- a/test/routes/graphql/Query/renewAuthenticationToken.test.ts +++ b/test/routes/graphql/Query/renewAuthenticationToken.test.ts @@ -70,7 +70,7 @@ suite("Query field renewAuthenticationToken", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, diff --git a/test/routes/graphql/Query/user.test.ts b/test/routes/graphql/Query/user.test.ts index 4c25206926..214d87e58e 100644 --- a/test/routes/graphql/Query/user.test.ts +++ b/test/routes/graphql/Query/user.test.ts @@ -85,7 +85,7 @@ suite("Query field user", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, diff --git a/test/routes/graphql/User/creator.test.ts b/test/routes/graphql/User/creator.test.ts index 39ec58c90f..82d945d1a1 100644 --- a/test/routes/graphql/User/creator.test.ts +++ b/test/routes/graphql/User/creator.test.ts @@ -92,7 +92,7 @@ suite("User field creator", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -152,7 +152,7 @@ suite("User field creator", () => { `results in a graphql error with "unauthorized_action" extensions code in the "errors" field and "null" as the value of "data.user.creator" field if`, () => { test(`client triggering the graphql operation is not associated to an administrator user. - argument "input.id" is not equal to the id of the existing user associated to the client triggering the graphql operation.`, async () => { + argument "input.id" is not equal to the id of the existing user associated to the client triggering the graphql operation.`, async () => { const administratorUserSignInResult = await mercuriusClient.query( Query_signIn, { @@ -181,7 +181,7 @@ suite("User field creator", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }), @@ -195,7 +195,7 @@ suite("User field creator", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }), @@ -239,38 +239,148 @@ suite("User field creator", () => { }, ); - test(`results in an empty "errors" field and the expected value for the "data.user.creator" field.`, async () => { - const administratorUserSignInResult = await mercuriusClient.query( - Query_signIn, - { - variables: { - input: { - emailAddress: server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, - password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + suite( + `results in an empty "errors" field and the expected value for the "data.user.creator" field where`, + () => { + test(`"data.user.updater" is "null" when the creator of the user no longer exists.`, async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + + const administratorUser0CreateUserResult = await mercuriusClient.mutate( + Mutation_createUser, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + emailAddress: `email${faker.string.nanoid()}@email.com`, + isEmailAddressVerified: false, + name: "name", + password: "password", + role: "administrator", + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUser0CreateUserResult.data.createUser + ?.authenticationToken, + ); + + const regularUser0CreateUserResult = await mercuriusClient.mutate( + Mutation_createUser, + { + headers: { + authorization: `bearer ${ + administratorUser0CreateUserResult.data.createUser + .authenticationToken + }`, + }, + variables: { + input: { + emailAddress: `email${faker.string.nanoid()}@email.com`, + isEmailAddressVerified: false, + name: "name", + password: "password", + role: "regular", + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUser0CreateUserResult.data.createUser.user?.id, + ); + + await mercuriusClient.mutate(Mutation_deleteUser, { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: administratorUser0CreateUserResult.data.createUser.user.id, + }, }, - }, - }, - ); - - assertToBeNonNullish( - administratorUserSignInResult.data.signIn?.authenticationToken, - ); - assertToBeNonNullish(administratorUserSignInResult.data.signIn.user?.id); - - const userCreatorResult = await mercuriusClient.query(Query_user_creator, { - headers: { - authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, - }, - variables: { - input: { - id: administratorUserSignInResult.data.signIn.user.id, - }, - }, - }); - - expect(userCreatorResult.errors).toBeUndefined(); - expect(userCreatorResult.data.user?.creator).toEqual( - administratorUserSignInResult.data.signIn.user, - ); - }); + }); + + assertToBeNonNullish( + regularUser0CreateUserResult.data.createUser?.user?.id, + ); + + const userCreatorResult = await mercuriusClient.query( + Query_user_creator, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: regularUser0CreateUserResult.data.createUser.user.id, + }, + }, + }, + ); + + expect(userCreatorResult.errors).toBeUndefined(); + expect(userCreatorResult.data.user?.creator).toEqual(null); + }); + + test(`"data.user.updater" is non-null when the creator of the user still exists.`, async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + assertToBeNonNullish( + administratorUserSignInResult.data.signIn.user?.id, + ); + + const userCreatorResult = await mercuriusClient.query( + Query_user_creator, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: administratorUserSignInResult.data.signIn.user.id, + }, + }, + }, + ); + + expect(userCreatorResult.errors).toBeUndefined(); + expect(userCreatorResult.data.user?.creator).toEqual( + administratorUserSignInResult.data.signIn.user, + ); + }); + }, + ); }); diff --git a/test/routes/graphql/User/updatedAt.test.ts b/test/routes/graphql/User/updatedAt.test.ts new file mode 100644 index 0000000000..08f64fd01c --- /dev/null +++ b/test/routes/graphql/User/updatedAt.test.ts @@ -0,0 +1,352 @@ +import { faker } from "@faker-js/faker"; +import { expect, suite, test } from "vitest"; +import type { + TalawaGraphQLFormattedError, + UnauthenticatedExtensions, + UnauthorizedActionExtensions, +} from "~/src/utilities/talawaGraphQLError"; +import { assertToBeNonNullish } from "../../../helpers"; +import { server } from "../../../server"; +import { mercuriusClient } from "../client"; +import { + Mutation_createUser, + Mutation_deleteUser, + Mutation_updateUser, + Query_signIn, + Query_user_updatedAt, +} from "../documentNodes"; + +suite("User field updatedAt", () => { + suite( + `results in a graphql error with "unauthenticated" extensions code in the "errors" field and "null" as the value of "data.user.updatedAt" field if`, + () => { + test("client triggering the graphql operation is not authenticated.", async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.user?.id, + ); + + const userUpdatedAtResult = await mercuriusClient.query( + Query_user_updatedAt, + { + variables: { + input: { + id: administratorUserSignInResult.data.signIn.user.id, + }, + }, + }, + ); + + expect(userUpdatedAtResult.data.user?.updatedAt).toEqual(null); + expect(userUpdatedAtResult.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + extensions: expect.objectContaining({ + code: "unauthenticated", + }), + message: expect.any(String), + path: ["user", "updatedAt"], + }), + ]), + ); + }); + + test("client triggering the graphql operation has no existing user associated to their authentication context.", async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + + const createUserResult = await mercuriusClient.mutate( + Mutation_createUser, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + emailAddress: `email${faker.string.nanoid()}@email.com`, + isEmailAddressVerified: false, + name: "name", + password: "password", + role: "regular", + }, + }, + }, + ); + + assertToBeNonNullish(createUserResult.data.createUser?.user?.id); + + await mercuriusClient.mutate(Mutation_deleteUser, { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: createUserResult.data.createUser.user.id, + }, + }, + }); + + assertToBeNonNullish( + createUserResult.data.createUser.authenticationToken, + ); + assertToBeNonNullish( + administratorUserSignInResult.data.signIn.user?.id, + ); + + const userUpdatedAtResult = await mercuriusClient.query( + Query_user_updatedAt, + { + headers: { + authorization: `bearer ${createUserResult.data.createUser.authenticationToken}`, + }, + variables: { + input: { + id: administratorUserSignInResult.data.signIn.user.id, + }, + }, + }, + ); + + expect(userUpdatedAtResult.data.user?.updatedAt).toEqual(null); + expect(userUpdatedAtResult.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + extensions: expect.objectContaining({ + code: "unauthenticated", + }), + message: expect.any(String), + path: ["user", "updatedAt"], + }), + ]), + ); + }); + }, + ); + + suite( + `results in a graphql error with "unauthorized_action" extensions code in the "errors" field and "null" as the value of "data.user.updatedAt" field if`, + () => { + test(`client triggering the graphql operation is not associated to an administrator user. + user associated to the argument "input.id" is not associated to the authentication context of the client triggering the graphql operation`, async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + + const createUserResult = await mercuriusClient.mutate( + Mutation_createUser, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + emailAddress: `email${faker.string.nanoid()}@email.com`, + isEmailAddressVerified: false, + name: "name", + password: "password", + role: "regular", + }, + }, + }, + ); + + assertToBeNonNullish( + createUserResult.data.createUser?.authenticationToken, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn.user?.id, + ); + + const userUpdatedAtResult = await mercuriusClient.query( + Query_user_updatedAt, + { + headers: { + authorization: `bearer ${createUserResult.data.createUser.authenticationToken}`, + }, + variables: { + input: { + id: administratorUserSignInResult.data.signIn.user.id, + }, + }, + }, + ); + + console.log("===================="); + console.log(userUpdatedAtResult.errors); + console.log("===================="); + + expect(userUpdatedAtResult.data.user?.updatedAt).toEqual(null); + expect(userUpdatedAtResult.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + extensions: expect.objectContaining( + { + code: "unauthorized_action", + }, + ), + message: expect.any(String), + path: ["user", "updatedAt"], + }), + ]), + ); + }); + }, + ); + + suite( + `results in an empty "errors" field and the expected value for the "data.user.updatedAt" field where`, + () => { + test(`"data.user.updatedAt" is "null" when the user has not been updated at least once after its creation.`, async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + assertToBeNonNullish( + administratorUserSignInResult.data.signIn.user?.id, + ); + + const userUpdatedAtResult = await mercuriusClient.mutate( + Query_user_updatedAt, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: administratorUserSignInResult.data.signIn.user.id, + }, + }, + }, + ); + + expect(userUpdatedAtResult.errors).toBeUndefined(); + expect(userUpdatedAtResult.data.user?.updatedAt).toEqual(null); + }); + + test(`"data.user.updatedAt" is non-null when the user has been updated at least once after its creation.`, async () => { + const administratorUserSignInResult = await mercuriusClient.query( + Query_signIn, + { + variables: { + input: { + emailAddress: + server.envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS, + password: server.envConfig.API_ADMINISTRATOR_USER_PASSWORD, + }, + }, + }, + ); + + assertToBeNonNullish( + administratorUserSignInResult.data.signIn?.authenticationToken, + ); + + const createUserResult = await mercuriusClient.mutate( + Mutation_createUser, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + emailAddress: `email${faker.string.nanoid()}@email.com`, + isEmailAddressVerified: false, + name: "name", + password: "password", + role: "regular", + }, + }, + }, + ); + + assertToBeNonNullish(createUserResult.data.createUser?.user?.id); + + const updateUserResult = await mercuriusClient.mutate( + Mutation_updateUser, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + address: null, + id: createUserResult.data.createUser.user.id, + }, + }, + }, + ); + + assertToBeNonNullish(updateUserResult.data.updateUser?.id); + + const userUpdatedAtResult = await mercuriusClient.query( + Query_user_updatedAt, + { + headers: { + authorization: `bearer ${administratorUserSignInResult.data.signIn.authenticationToken}`, + }, + variables: { + input: { + id: updateUserResult.data.updateUser.id, + }, + }, + }, + ); + + expect(userUpdatedAtResult.errors).toBeUndefined(); + expect(userUpdatedAtResult.data.user?.updatedAt).toBeTypeOf("string"); + }); + }, + ); +}); diff --git a/test/routes/graphql/User/updater.test.ts b/test/routes/graphql/User/updater.test.ts index bad4f00ad9..6a69ccef40 100644 --- a/test/routes/graphql/User/updater.test.ts +++ b/test/routes/graphql/User/updater.test.ts @@ -93,7 +93,7 @@ suite("User field updater", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -183,7 +183,7 @@ suite("User field updater", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, @@ -301,7 +301,7 @@ suite("User field updater", () => { isEmailAddressVerified: false, name: "name", password: "password", - role: "base", + role: "regular", }, }, }, diff --git a/test/routes/graphql/documentNodes.ts b/test/routes/graphql/documentNodes.ts index 3cd0db3537..662b7b70c0 100644 --- a/test/routes/graphql/documentNodes.ts +++ b/test/routes/graphql/documentNodes.ts @@ -34,7 +34,6 @@ export const Mutation_createUser = postalCode role state - updatedAt workPhoneNumber } } @@ -63,7 +62,6 @@ export const Mutation_deleteCurrentUser = postalCode role state - updatedAt workPhoneNumber } }`); @@ -91,7 +89,6 @@ export const Mutation_deleteUser = postalCode role state - updatedAt workPhoneNumber } }`); @@ -121,7 +118,6 @@ export const Mutation_signUp = postalCode role state - updatedAt workPhoneNumber } } @@ -150,7 +146,6 @@ export const Mutation_updateCurrentUser = postalCode role state - updatedAt workPhoneNumber } }`); @@ -178,7 +173,6 @@ export const Mutation_updateUser = postalCode role state - updatedAt workPhoneNumber } }`); @@ -205,7 +199,6 @@ export const Query_currentUser = gql(`query Query_currentUser { postalCode role state - updatedAt workPhoneNumber } }`); @@ -239,7 +232,6 @@ export const Query_signIn = gql(`query Query_signIn($input: QuerySignInInput!) { postalCode role state - updatedAt workPhoneNumber } } @@ -267,7 +259,6 @@ export const Query_user = gql(`query Query_user($input: QueryUserInput!) { postalCode role state - updatedAt workPhoneNumber } }`); @@ -296,12 +287,18 @@ export const Query_user_creator = postalCode role state - updatedAt workPhoneNumber } } }`); +export const Query_user_updatedAt = + gql(`query Query_user_updatedAt($input: QueryUserInput!) { + user(input: $input) { + updatedAt + } +}`); + export const Query_user_updater = gql(`query Query_user_updater($input: QueryUserInput!) { user(input: $input) { @@ -326,7 +323,6 @@ export const Query_user_updater = postalCode role state - updatedAt workPhoneNumber } } diff --git a/test/routes/graphql/gql.tada-cache.d.ts b/test/routes/graphql/gql.tada-cache.d.ts index aa1f5984be..929c71a6bd 100644 --- a/test/routes/graphql/gql.tada-cache.d.ts +++ b/test/routes/graphql/gql.tada-cache.d.ts @@ -4,7 +4,31 @@ import type { TadaDocumentNode, $tada } from 'gql.tada'; declare module 'gql.tada' { interface setupCache { - "query helloQuery($name: String!) {\n hello(name: $name)\n}": - TadaDocumentNode<{ hello: string | null; }, { name: string; }, void>; + "mutation Mutation_createUser($input: MutationCreateUserInput!) {\n createUser(input: $input){\n authenticationToken\n user {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber\n }\n }\n}": + TadaDocumentNode<{ createUser: { authenticationToken: string | null; user: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; } | null; }, { input: { workPhoneNumber?: string | null | undefined; state?: string | null | undefined; role: "administrator" | "regular"; postalCode?: string | null | undefined; password: string; natalSex?: "female" | "intersex" | "male" | null | undefined; name: string; mobilePhoneNumber?: string | null | undefined; maritalStatus?: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null | undefined; isEmailAddressVerified: boolean; homePhoneNumber?: string | null | undefined; employmentStatus?: "full_time" | "part_time" | "unemployed" | null | undefined; emailAddress: string; educationGrade?: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null | undefined; description?: string | null | undefined; countryCode?: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null | undefined; city?: string | null | undefined; birthDate?: string | null | undefined; avatarURI?: string | null | undefined; address?: string | null | undefined; }; }, void>; + "mutation Mutation_deleteCurrentUser {\n deleteCurrentUser {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n}": + TadaDocumentNode<{ deleteCurrentUser: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, {}, void>; + "mutation Mutation_deleteUser($input: MutationDeleteUserInput!) {\n deleteUser(input: $input) {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n}": + TadaDocumentNode<{ deleteUser: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, { input: { id: string; }; }, void>; + "mutation Mutation_signUp($input: MutationSignUpInput!) {\n signUp(input: $input) {\n authenticationToken\n user {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber\n }\n }\n}": + TadaDocumentNode<{ signUp: { authenticationToken: string | null; user: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; } | null; }, { input: { workPhoneNumber?: string | null | undefined; state?: string | null | undefined; postalCode?: string | null | undefined; password: string; natalSex?: "female" | "intersex" | "male" | null | undefined; name: string; mobilePhoneNumber?: string | null | undefined; maritalStatus?: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null | undefined; homePhoneNumber?: string | null | undefined; employmentStatus?: "full_time" | "part_time" | "unemployed" | null | undefined; emailAddress: string; educationGrade?: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null | undefined; description?: string | null | undefined; countryCode?: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null | undefined; confirmedPassword: string; city?: string | null | undefined; birthDate?: string | null | undefined; avatarURI?: string | null | undefined; address?: string | null | undefined; }; }, void>; + "mutation Mutation_updateCurrentUser($input: MutationUpdateCurrentUserInput!) {\n updateCurrentUser(input: $input) {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n}": + TadaDocumentNode<{ updateCurrentUser: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, { input: { workPhoneNumber?: string | null | undefined; state?: string | null | undefined; postalCode?: string | null | undefined; password?: string | null | undefined; natalSex?: "female" | "intersex" | "male" | null | undefined; name?: string | null | undefined; mobilePhoneNumber?: string | null | undefined; maritalStatus?: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null | undefined; homePhoneNumber?: string | null | undefined; employmentStatus?: "full_time" | "part_time" | "unemployed" | null | undefined; emailAddress?: string | null | undefined; educationGrade?: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null | undefined; description?: string | null | undefined; countryCode?: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null | undefined; city?: string | null | undefined; birthDate?: string | null | undefined; avatarURI?: string | null | undefined; address?: string | null | undefined; }; }, void>; + "mutation Mutation_updateUser($input: MutationUpdateUserInput!) {\n updateUser(input: $input) {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n}": + TadaDocumentNode<{ updateUser: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, { input: { workPhoneNumber?: string | null | undefined; state?: string | null | undefined; role?: "administrator" | "regular" | null | undefined; postalCode?: string | null | undefined; password?: string | null | undefined; natalSex?: "female" | "intersex" | "male" | null | undefined; name?: string | null | undefined; mobilePhoneNumber?: string | null | undefined; maritalStatus?: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null | undefined; isEmailAddressVerified?: boolean | null | undefined; id: string; homePhoneNumber?: string | null | undefined; employmentStatus?: "full_time" | "part_time" | "unemployed" | null | undefined; emailAddress?: string | null | undefined; educationGrade?: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null | undefined; description?: string | null | undefined; countryCode?: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null | undefined; city?: string | null | undefined; birthDate?: string | null | undefined; avatarURI?: string | null | undefined; address?: string | null | undefined; }; }, void>; + "query Query_currentUser {\n currentUser {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber\n }\n}": + TadaDocumentNode<{ currentUser: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, {}, void>; + "query Query_renewAuthenticationToken {\n renewAuthenticationToken\n}": + TadaDocumentNode<{ renewAuthenticationToken: string | null; }, {}, void>; + "query Query_signIn($input: QuerySignInInput!) {\n signIn(input: $input) {\n authenticationToken\n user {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber\n }\n }\n}": + TadaDocumentNode<{ signIn: { authenticationToken: string | null; user: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; } | null; }, { input: { password: string; emailAddress: string; }; }, void>; + "query Query_user($input: QueryUserInput!) {\n user(input: $input) {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber\n }\n}": + TadaDocumentNode<{ user: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; }, { input: { id: string; }; }, void>; + "query Query_user_creator($input: QueryUserInput!) {\n user(input: $input) {\n creator {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n }\n}": + TadaDocumentNode<{ user: { creator: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; } | null; }, { input: { id: string; }; }, void>; + "query Query_user_updatedAt($input: QueryUserInput!) {\n user(input: $input) {\n updatedAt\n }\n}": + TadaDocumentNode<{ user: { updatedAt: string | null; } | null; }, { input: { id: string; }; }, void>; + "query Query_user_updater($input: QueryUserInput!) {\n user(input: $input) {\n updater {\n address\n avatarURI\n birthDate\n city\n countryCode\n createdAt\n description\n educationGrade\n emailAddress\n employmentStatus\n homePhoneNumber\n id\n isEmailAddressVerified\n maritalStatus\n mobilePhoneNumber\n name\n natalSex\n postalCode\n role\n state\n workPhoneNumber \n }\n }\n}": + TadaDocumentNode<{ user: { updater: { address: string | null; avatarURI: string | null; birthDate: string | null; city: string | null; countryCode: "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "at" | "au" | "aw" | "ax" | "az" | "ba" | "bb" | "bd" | "be" | "bf" | "bg" | "bh" | "bi" | "bj" | "bl" | "bm" | "bn" | "bo" | "bq" | "br" | "bs" | "bt" | "bv" | "bw" | "by" | "bz" | "ca" | "cc" | "cd" | "cf" | "cg" | "ch" | "ci" | "ck" | "cl" | "cm" | "cn" | "co" | "cr" | "cu" | "cv" | "cw" | "cx" | "cy" | "cz" | "de" | "dj" | "dk" | "dm" | "do" | "dz" | "ec" | "ee" | "eg" | "eh" | "er" | "es" | "et" | "fi" | "fj" | "fk" | "fm" | "fo" | "fr" | "ga" | "gb" | "gd" | "ge" | "gf" | "gg" | "gh" | "gi" | "gl" | "gm" | "gn" | "gp" | "gq" | "gr" | "gs" | "gt" | "gu" | "gw" | "gy" | "hk" | "hm" | "hn" | "hr" | "ht" | "hu" | "id" | "ie" | "il" | "im" | "in" | "io" | "iq" | "ir" | "is" | "it" | "je" | "jm" | "jo" | "jp" | "ke" | "kg" | "kh" | "ki" | "km" | "kn" | "kp" | "kr" | "kw" | "ky" | "kz" | "la" | "lb" | "lc" | "li" | "lk" | "lr" | "ls" | "lt" | "lu" | "lv" | "ly" | "ma" | "mc" | "md" | "me" | "mf" | "mg" | "mh" | "mk" | "ml" | "mm" | "mn" | "mo" | "mp" | "mq" | "mr" | "ms" | "mt" | "mu" | "mv" | "mw" | "mx" | "my" | "mz" | "na" | "nc" | "ne" | "nf" | "ng" | "ni" | "nl" | "no" | "np" | "nr" | "nu" | "nz" | "om" | "pa" | "pe" | "pf" | "pg" | "ph" | "pk" | "pl" | "pm" | "pn" | "pr" | "ps" | "pt" | "pw" | "py" | "qa" | "re" | "ro" | "rs" | "ru" | "rw" | "sa" | "sb" | "sc" | "sd" | "se" | "sg" | "sh" | "si" | "sj" | "sk" | "sl" | "sm" | "sn" | "so" | "sr" | "ss" | "st" | "sv" | "sx" | "sy" | "sz" | "tc" | "td" | "tf" | "tg" | "th" | "tj" | "tk" | "tl" | "tm" | "tn" | "to" | "tr" | "tt" | "tv" | "tw" | "tz" | "ua" | "ug" | "um" | "us" | "uy" | "uz" | "va" | "vc" | "ve" | "vg" | "vi" | "vn" | "vu" | "wf" | "ws" | "ye" | "yt" | "za" | "zm" | "zw" | null; createdAt: string | null; description: string | null; educationGrade: "kg" | "grade_1" | "grade_2" | "grade_3" | "grade_4" | "grade_5" | "grade_6" | "grade_7" | "grade_8" | "grade_9" | "grade_10" | "grade_11" | "grade_12" | "graduate" | "no_grade" | "pre_kg" | null; emailAddress: string | null; employmentStatus: "full_time" | "part_time" | "unemployed" | null; homePhoneNumber: string | null; id: string; isEmailAddressVerified: boolean | null; maritalStatus: "divorced" | "engaged" | "married" | "seperated" | "single" | "widowed" | null; mobilePhoneNumber: string | null; name: string | null; natalSex: "female" | "intersex" | "male" | null; postalCode: string | null; role: "administrator" | "regular" | null; state: string | null; workPhoneNumber: string | null; } | null; } | null; }, { input: { id: string; }; }, void>; } } diff --git a/test/routes/graphql/gql.tada.d.ts b/test/routes/graphql/gql.tada.d.ts index c4b8627892..a7d1658a2c 100644 --- a/test/routes/graphql/gql.tada.d.ts +++ b/test/routes/graphql/gql.tada.d.ts @@ -9,14 +9,19 @@ export type introspection_types = { 'EmailAddress': unknown; 'ID': unknown; 'Iso3166Alpha2CountryCode': { name: 'Iso3166Alpha2CountryCode'; enumValues: 'ad' | 'ae' | 'af' | 'ag' | 'ai' | 'al' | 'am' | 'ao' | 'aq' | 'ar' | 'as' | 'at' | 'au' | 'aw' | 'ax' | 'az' | 'ba' | 'bb' | 'bd' | 'be' | 'bf' | 'bg' | 'bh' | 'bi' | 'bj' | 'bl' | 'bm' | 'bn' | 'bo' | 'bq' | 'br' | 'bs' | 'bt' | 'bv' | 'bw' | 'by' | 'bz' | 'ca' | 'cc' | 'cd' | 'cf' | 'cg' | 'ch' | 'ci' | 'ck' | 'cl' | 'cm' | 'cn' | 'co' | 'cr' | 'cu' | 'cv' | 'cw' | 'cx' | 'cy' | 'cz' | 'de' | 'dj' | 'dk' | 'dm' | 'do' | 'dz' | 'ec' | 'ee' | 'eg' | 'eh' | 'er' | 'es' | 'et' | 'fi' | 'fj' | 'fk' | 'fm' | 'fo' | 'fr' | 'ga' | 'gb' | 'gd' | 'ge' | 'gf' | 'gg' | 'gh' | 'gi' | 'gl' | 'gm' | 'gn' | 'gp' | 'gq' | 'gr' | 'gs' | 'gt' | 'gu' | 'gw' | 'gy' | 'hk' | 'hm' | 'hn' | 'hr' | 'ht' | 'hu' | 'id' | 'ie' | 'il' | 'im' | 'in' | 'io' | 'iq' | 'ir' | 'is' | 'it' | 'je' | 'jm' | 'jo' | 'jp' | 'ke' | 'kg' | 'kh' | 'ki' | 'km' | 'kn' | 'kp' | 'kr' | 'kw' | 'ky' | 'kz' | 'la' | 'lb' | 'lc' | 'li' | 'lk' | 'lr' | 'ls' | 'lt' | 'lu' | 'lv' | 'ly' | 'ma' | 'mc' | 'md' | 'me' | 'mf' | 'mg' | 'mh' | 'mk' | 'ml' | 'mm' | 'mn' | 'mo' | 'mp' | 'mq' | 'mr' | 'ms' | 'mt' | 'mu' | 'mv' | 'mw' | 'mx' | 'my' | 'mz' | 'na' | 'nc' | 'ne' | 'nf' | 'ng' | 'ni' | 'nl' | 'no' | 'np' | 'nr' | 'nu' | 'nz' | 'om' | 'pa' | 'pe' | 'pf' | 'pg' | 'ph' | 'pk' | 'pl' | 'pm' | 'pn' | 'pr' | 'ps' | 'pt' | 'pw' | 'py' | 'qa' | 're' | 'ro' | 'rs' | 'ru' | 'rw' | 'sa' | 'sb' | 'sc' | 'sd' | 'se' | 'sg' | 'sh' | 'si' | 'sj' | 'sk' | 'sl' | 'sm' | 'sn' | 'so' | 'sr' | 'ss' | 'st' | 'sv' | 'sx' | 'sy' | 'sz' | 'tc' | 'td' | 'tf' | 'tg' | 'th' | 'tj' | 'tk' | 'tl' | 'tm' | 'tn' | 'to' | 'tr' | 'tt' | 'tv' | 'tw' | 'tz' | 'ua' | 'ug' | 'um' | 'us' | 'uy' | 'uz' | 'va' | 'vc' | 've' | 'vg' | 'vi' | 'vn' | 'vu' | 'wf' | 'ws' | 'ye' | 'yt' | 'za' | 'zm' | 'zw'; }; - 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'createUser': { name: 'createUser'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'deleteCurrentUser': { name: 'deleteCurrentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'deleteUser': { name: 'deleteUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'signUp': { name: 'signUp'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'updateCurrentUser': { name: 'updateCurrentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'updateUser': { name: 'updateUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; }; + 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'createOrganization': { name: 'createOrganization'; type: { kind: 'OBJECT'; name: 'Organization'; ofType: null; } }; 'createUser': { name: 'createUser'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'deleteCurrentUser': { name: 'deleteCurrentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'deleteOrganization': { name: 'deleteOrganization'; type: { kind: 'OBJECT'; name: 'Organization'; ofType: null; } }; 'deleteUser': { name: 'deleteUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'signUp': { name: 'signUp'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'updateCurrentUser': { name: 'updateCurrentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'updateOrganization': { name: 'updateOrganization'; type: { kind: 'OBJECT'; name: 'Organization'; ofType: null; } }; 'updateUser': { name: 'updateUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; }; + 'MutationCreateOrganizationInput': { kind: 'INPUT_OBJECT'; name: 'MutationCreateOrganizationInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; 'MutationCreateUserInput': { kind: 'INPUT_OBJECT'; name: 'MutationCreateUserInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'birthDate'; type: { kind: 'SCALAR'; name: 'Date'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'educationGrade'; type: { kind: 'ENUM'; name: 'UserEducationGrade'; ofType: null; }; defaultValue: null }, { name: 'emailAddress'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'EmailAddress'; ofType: null; }; }; defaultValue: null }, { name: 'employmentStatus'; type: { kind: 'ENUM'; name: 'UserEmploymentStatus'; ofType: null; }; defaultValue: null }, { name: 'homePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'isEmailAddressVerified'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'maritalStatus'; type: { kind: 'ENUM'; name: 'UserMaritalStatus'; ofType: null; }; defaultValue: null }, { name: 'mobilePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'natalSex'; type: { kind: 'ENUM'; name: 'UserNatalSex'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'UserRole'; ofType: null; }; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'workPhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }]; }; + 'MutationDeleteOrganizationInput': { kind: 'INPUT_OBJECT'; name: 'MutationDeleteOrganizationInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }]; }; 'MutationDeleteUserInput': { kind: 'INPUT_OBJECT'; name: 'MutationDeleteUserInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }]; }; 'MutationSignUpInput': { kind: 'INPUT_OBJECT'; name: 'MutationSignUpInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'birthDate'; type: { kind: 'SCALAR'; name: 'Date'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'confirmedPassword'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'educationGrade'; type: { kind: 'ENUM'; name: 'UserEducationGrade'; ofType: null; }; defaultValue: null }, { name: 'emailAddress'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'EmailAddress'; ofType: null; }; }; defaultValue: null }, { name: 'employmentStatus'; type: { kind: 'ENUM'; name: 'UserEmploymentStatus'; ofType: null; }; defaultValue: null }, { name: 'homePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'maritalStatus'; type: { kind: 'ENUM'; name: 'UserMaritalStatus'; ofType: null; }; defaultValue: null }, { name: 'mobilePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'natalSex'; type: { kind: 'ENUM'; name: 'UserNatalSex'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'workPhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }]; }; 'MutationUpdateCurrentUserInput': { kind: 'INPUT_OBJECT'; name: 'MutationUpdateCurrentUserInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'birthDate'; type: { kind: 'SCALAR'; name: 'Date'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'educationGrade'; type: { kind: 'ENUM'; name: 'UserEducationGrade'; ofType: null; }; defaultValue: null }, { name: 'emailAddress'; type: { kind: 'SCALAR'; name: 'EmailAddress'; ofType: null; }; defaultValue: null }, { name: 'employmentStatus'; type: { kind: 'ENUM'; name: 'UserEmploymentStatus'; ofType: null; }; defaultValue: null }, { name: 'homePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'maritalStatus'; type: { kind: 'ENUM'; name: 'UserMaritalStatus'; ofType: null; }; defaultValue: null }, { name: 'mobilePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'natalSex'; type: { kind: 'ENUM'; name: 'UserNatalSex'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'workPhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }]; }; + 'MutationUpdateOrganizationInput': { kind: 'INPUT_OBJECT'; name: 'MutationUpdateOrganizationInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; 'MutationUpdateUserInput': { kind: 'INPUT_OBJECT'; name: 'MutationUpdateUserInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'birthDate'; type: { kind: 'SCALAR'; name: 'Date'; ofType: null; }; defaultValue: null }, { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'educationGrade'; type: { kind: 'ENUM'; name: 'UserEducationGrade'; ofType: null; }; defaultValue: null }, { name: 'emailAddress'; type: { kind: 'SCALAR'; name: 'EmailAddress'; ofType: null; }; defaultValue: null }, { name: 'employmentStatus'; type: { kind: 'ENUM'; name: 'UserEmploymentStatus'; ofType: null; }; defaultValue: null }, { name: 'homePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'isEmailAddressVerified'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'maritalStatus'; type: { kind: 'ENUM'; name: 'UserMaritalStatus'; ofType: null; }; defaultValue: null }, { name: 'mobilePhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'natalSex'; type: { kind: 'ENUM'; name: 'UserNatalSex'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'ENUM'; name: 'UserRole'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'workPhoneNumber'; type: { kind: 'SCALAR'; name: 'PhoneNumber'; ofType: null; }; defaultValue: null }]; }; + 'Organization': { kind: 'OBJECT'; name: 'Organization'; fields: { 'address': { name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'avatarURI': { name: 'avatarURI'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'city': { name: 'city'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'countryCode': { name: 'countryCode'; type: { kind: 'ENUM'; name: 'Iso3166Alpha2CountryCode'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'creator': { name: 'creator'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'postalCode': { name: 'postalCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'state': { name: 'state'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'updater': { name: 'updater'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; }; 'PhoneNumber': unknown; - 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'currentUser': { name: 'currentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'renewAuthenticationToken': { name: 'renewAuthenticationToken'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'signIn': { name: 'signIn'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'user': { name: 'user'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'currentUser': { name: 'currentUser'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'organization': { name: 'organization'; type: { kind: 'OBJECT'; name: 'Organization'; ofType: null; } }; 'renewAuthenticationToken': { name: 'renewAuthenticationToken'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'signIn': { name: 'signIn'; type: { kind: 'OBJECT'; name: 'AuthenticationPayload'; ofType: null; } }; 'user': { name: 'user'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; }; }; + 'QueryOrganizationInput': { kind: 'INPUT_OBJECT'; name: 'QueryOrganizationInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; 'QuerySignInInput': { kind: 'INPUT_OBJECT'; name: 'QuerySignInInput'; isOneOf: false; inputFields: [{ name: 'emailAddress'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'EmailAddress'; ofType: null; }; }; defaultValue: null }, { name: 'password'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; 'QueryUserInput': { kind: 'INPUT_OBJECT'; name: 'QueryUserInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; 'String': unknown; @@ -25,7 +30,7 @@ export type introspection_types = { 'UserEmploymentStatus': { name: 'UserEmploymentStatus'; enumValues: 'full_time' | 'part_time' | 'unemployed'; }; 'UserMaritalStatus': { name: 'UserMaritalStatus'; enumValues: 'divorced' | 'engaged' | 'married' | 'seperated' | 'single' | 'widowed'; }; 'UserNatalSex': { name: 'UserNatalSex'; enumValues: 'female' | 'intersex' | 'male'; }; - 'UserRole': { name: 'UserRole'; enumValues: 'administrator' | 'base'; }; + 'UserRole': { name: 'UserRole'; enumValues: 'administrator' | 'regular'; }; }; /** An IntrospectionQuery representation of your schema.