From 355789b6b87e29021da7a465a7aac08a195b330f Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 23 Feb 2025 01:05:00 +0530 Subject: [PATCH] Added changes for No-MongoPost Signed-off-by: NishantSinghhhhh --- sample_data/post.json | 450 ++++++++++++++++++ schema.graphql | 19 +- src/graphql/inputs/MutationUpdatePostInput.ts | 24 +- src/graphql/types/Post/index.ts | 1 + src/graphql/types/Post/sorting.ts | 62 +++ src/utilities/loadSampleData.ts | 314 +++++++----- test/routes/graphql/gql.tada-cache.d.ts | 6 + 7 files changed, 746 insertions(+), 130 deletions(-) create mode 100644 sample_data/post.json create mode 100644 src/graphql/types/Post/sorting.ts diff --git a/sample_data/post.json b/sample_data/post.json new file mode 100644 index 00000000000..80a8d35229f --- /dev/null +++ b/sample_data/post.json @@ -0,0 +1,450 @@ +[ + { + "id": "67378abd-8500-8f17-1cf2-990d00000010", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Test Information for Test Post", + "title": "Test Post", + "creatorId": "658930fd-2caa-9d8d-6908-745c00000008", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000011", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Daring climb up majestic peaks, crisp mountain air, breathtaking vistas—adventure awaits with every step, an adrenaline-filled journey.", + "title": "Thrilling Mountain Expedition", + "creatorId": "67378abd-8500-8f17-1cf2-990d00000006", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000012", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Tranquil seas, sun's descent paints hues of serenity. A moment of pure bliss, waves whispering a peaceful lullaby.", + "title": "Sunset Serenity by the Sea", + "creatorId": "658938a6-2caa-9d8d-6908-74880000000d", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000013", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Deep into lush rainforest, vibrant flora, exotic creatures create a magical ambiance—nature's enchantment revealed in every step.", + "title": "Enchanting Rainforest Discovery", + "creatorId": "67378abd-8500-8f17-1cf2-990d00000007", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000014", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Crafting coffee as art, each cup tells a unique story through carefully selected beans, brewing techniques—a sip of creativity.", + "title": "Artistic Coffee Moments", + "creatorId": "6589389d-2caa-9d8d-6908-74870000000c", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000015", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Roaming city streets, lights come alive, capturing vibrant urban energy—each corner holds a story, a cityscape adventure unfolds.", + "title": "Urban Exploration: City Lights", + "creatorId": "6589387e-2caa-9d8d-6908-74850000000a", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000016", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Solace by a lakeside, soothing sounds of nature, gentle lapping water—tranquility found in the embrace of serene waters.", + "title": "Tranquil Lakeside Retreat", + "creatorId": "658938ba-2caa-9d8d-6908-748a0000000f", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000017", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Soaring above clouds in a hot air balloon, world seen from a breathtaking perspective—sky-high adventure, a journey to remember.", + "title": "Aerial Adventures: Hot Air Balloon Ride", + "creatorId": "67378abd-8500-8f17-1cf2-990d00000005", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000018", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Navigating dusty shelves, discovering hidden gems in quaint bookstores—a haven for literary enthusiasts, where stories come alive.", + "title": "Literary Escapade in Old Bookstores", + "creatorId": "658938a6-2caa-9d8d-6908-74880000000d", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000019", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Exploring seaside towns, indulging in fresh seafood—each dish a delectable journey through local coastal flavors, a culinary escapade.", + "title": "Coastal Culinary Delights", + "creatorId": "6589389d-2caa-9d8d-6908-74870000000c", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000020", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Camping beneath desert sky, towering sand dunes, celestial display of stars—starry nights in a desert oasis, a magical experience.", + "title": "Desert Oasis: Sand Dunes and Starry Nights", + "creatorId": "658930fd-2caa-9d8d-6908-745c00000008", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000021", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Roaming vibrant neighborhoods adorned with captivating street art—each mural tells a unique story, an urban canvas filled with creativity.", + "title": "Street Art Extravaganza", + "creatorId": "6589387e-2caa-9d8d-6908-74850000000a", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000022", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Unwinding in a cozy mountain cabin, surrounded by snow-capped peaks and the crisp scent of pine—mountain retreat, pure relaxation.", + "title": "Mountain Cabin Getaway", + "creatorId": "658938ba-2caa-9d8d-6908-748a0000000f", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000023", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Exploring mesmerizing underwater world, encountering coral reefs, fascinating marine life—deep-sea diving, a journey into underwater wonders.", + "title": "Deep Sea Diving: Underwater Wonders", + "creatorId": "6589388b-2caa-9d8d-6908-74860000000b", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000024", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Walking through ancient ruins, a journey through time—weathered remnants of civilizations, archaeological wonders, history unfolding.", + "title": "Historical Treasures: Ancient Ruins", + "creatorId": "67378abd-8500-8f17-1cf2-990d00000007", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000025", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": false, + "text": "Sampling diverse global cuisines, a gastronomic adventure transcending borders—tantalizing taste buds, a journey of culinary fusion.", + "title": "Culinary Fusion: Global Flavors", + "creatorId": "6589386a-2caa-9d8d-6908-748400000009", + "organizationId": "ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5f", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000026", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Capturing the tranquility of snowflakes dancing in the crisp winter air.", + "title": "Snowflake Serenity", + "creatorId": "6589386a-2caa-9d8d-6908-748400000009", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-13T05:17:14.396Z", + "updatedAt": "2023-04-13T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000027", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Exploring the magic of winter nights with shimmering stars lighting up a frosty wonderland.", + "title": "Starry Winter Nights", + "creatorId": "6589388b-2caa-9d8d-6908-74860000000b", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-02T05:17:14.396Z", + "updatedAt": "2023-04-02T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000028", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "The charm of early spring peeking through the snow, a hint of rebirth in a cold embrace.", + "title": "Spring's Whisper in Winter", + "creatorId": "658938a6-2caa-9d8d-6908-74880000000d", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-03T05:17:14.396Z", + "updatedAt": "2023-04-03T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000029", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "A snowy path leads to warm stories by the fire—wintertime tales of joy and togetherness.", + "title": "Snowy Path to Warmth", + "creatorId": "658930fd-2caa-9d8d-6908-745c00000008", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-04T05:17:14.396Z", + "updatedAt": "2023-04-04T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000030", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Frosty mornings awaken the spirit of adventure, trekking through the snow-clad wilderness.", + "title": "Frosty Morning Treks", + "creatorId": "67378abd-8500-8f17-1cf2-990d00000007", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-05T05:17:14.396Z", + "updatedAt": "2023-04-05T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000031", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "The joy of building snowmen with loved ones, memories frozen in time.", + "title": "Snowman Chronicles", + "creatorId": "658938b0-2caa-9d8d-6908-74890000000e", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-06T05:17:14.396Z", + "updatedAt": "2023-04-06T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000032", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Skiing down majestic slopes, a thrilling embrace of nature’s winter canvas.", + "title": "Skiing Adventures", + "creatorId": "6589387e-2caa-9d8d-6908-74850000000a", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-07T05:17:14.396Z", + "updatedAt": "2023-04-07T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000033", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Winter brings its culinary wonders, a feast of comfort and joy.", + "title": "Winter Feast Delights", + "creatorId": "6589389d-2caa-9d8d-6908-74870000000c", + "organizationId": "cd3e4f5b-6a7c-8d9e-0f1a-2b3c4d5e6f7a", + "imageUrl": null, + "createdAt": "2023-04-08T05:17:14.396Z", + "updatedAt": "2023-04-08T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000034", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Exploring frozen lakes and finding reflections of serenity in icy waters.", + "title": "Reflections on Ice", + "creatorId": "6589386a-2caa-9d8d-6908-748400000009", + "organizationId": "bc2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f", + "imageUrl": null, + "createdAt": "2023-04-09T05:17:14.396Z", + "updatedAt": "2023-04-09T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000035", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "The quiet beauty of snow-covered forests, where nature whispers its secrets.", + "title": "Snowy Forest Tales", + "creatorId": "658938ba-2caa-9d8d-6908-748a0000000f", + "organizationId": "bc2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f", + "imageUrl": null, + "createdAt": "2023-04-10T05:17:14.396Z", + "updatedAt": "2023-04-10T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000036", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "Witnessing the ethereal glow of the northern lights in the heart of winter.", + "title": "Aurora Borealis Bliss", + "creatorId": "658938b0-2caa-9d8d-6908-74890000000e", + "organizationId": "bc2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f", + "imageUrl": null, + "createdAt": "2023-04-11T05:17:14.396Z", + "updatedAt": "2023-04-11T05:17:14.396Z" + }, + { + "id": "67378abd-8500-8f17-1cf2-990d00000037", + "status": "ACTIVE", + "likedBy": [], + "comments": [], + "likeCount": 0, + "commentCount": 0, + "pinned": true, + "text": "A journey through winter markets, where warmth meets festivity.", + "title": "Winter Market Magic", + "creatorId": "6589387e-2caa-9d8d-6908-74850000000a", + "organizationId": "bc2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f", + "imageUrl": null, + "createdAt": "2023-04-12T05:17:14.396Z", + "updatedAt": "2023-04-12T05:17:14.396Z" + } +] \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index ce1522b2e0e..b3736fafefa 100644 --- a/schema.graphql +++ b/schema.graphql @@ -163,6 +163,14 @@ enum AgendaItemType { song } +input AttachmentInput { + """Mime type of the attachment.""" + mimeType: String! + + """URL of the attachment.""" + url: String! +} + """""" type AuthenticationPayload { """ @@ -611,6 +619,11 @@ type FundCampaignsConnectionEdge { node: FundCampaign } +input GetPostsByOrgInput { + organizationId: String! + sortOrder: String +} + """ Possible variants of the two-letter language code defined in ISO 639-1, part of the ISO 639 standard published by the International Organization for Standardization (ISO), to represent natural languages. """ @@ -2352,8 +2365,11 @@ input MutationUpdateOrganizationMembershipInput { role: OrganizationMembershipRole } -"""""" +"""Input for updating a post.""" input MutationUpdatePostInput { + """Array of attachments (mimeType and URL).""" + attachments: [AttachmentInput!] + """Caption about the post.""" caption: String @@ -2873,6 +2889,7 @@ type Query { """Query field to read a post.""" post(input: QueryPostInput!): Post + postsByOrganization(input: GetPostsByOrgInput!): [Post!] """ Query field to renew the authentication token of an authenticated client for signing in to talawa. diff --git a/src/graphql/inputs/MutationUpdatePostInput.ts b/src/graphql/inputs/MutationUpdatePostInput.ts index d073a13a6ea..f03cd104b49 100644 --- a/src/graphql/inputs/MutationUpdatePostInput.ts +++ b/src/graphql/inputs/MutationUpdatePostInput.ts @@ -2,11 +2,17 @@ import { z } from "zod"; import { postsTableInsertSchema } from "~/src/drizzle/tables/posts"; import { builder } from "~/src/graphql/builder"; +const attachmentSchema = z.object({ + mimeType: z.string(), + url: z.string(), +}); + export const mutationUpdatePostInputSchema = z .object({ caption: postsTableInsertSchema.shape.caption.optional(), id: postsTableInsertSchema.shape.id.unwrap(), isPinned: z.boolean().optional(), + attachments: z.array(attachmentSchema).optional(), }) .refine( ({ id, ...remainingArg }) => @@ -16,12 +22,24 @@ export const mutationUpdatePostInputSchema = z }, ); +const AttachmentInput = builder + .inputRef<{ mimeType: string; url: string }>("AttachmentInput") + .implement({ + fields: (t) => ({ + mimeType: t.string({ + required: true, + description: "Mime type of the attachment.", + }), + url: t.string({ required: true, description: "URL of the attachment." }), + }), + }); + export const MutationUpdatePostInput = builder .inputRef>( "MutationUpdatePostInput", ) .implement({ - description: "", + description: "Input for updating a post.", fields: (t) => ({ caption: t.string({ description: "Caption about the post.", @@ -33,5 +51,9 @@ export const MutationUpdatePostInput = builder isPinned: t.boolean({ description: "Boolean to tell if the post is pinned", }), + attachments: t.field({ + type: [AttachmentInput], + description: "Array of attachments (mimeType and URL).", + }), }), }); diff --git a/src/graphql/types/Post/index.ts b/src/graphql/types/Post/index.ts index 153cb584fbe..96843948ae9 100644 --- a/src/graphql/types/Post/index.ts +++ b/src/graphql/types/Post/index.ts @@ -8,3 +8,4 @@ import "./organization"; import "./updater"; import "./upVoters"; import "./upVotesCount"; +import "./sorting"; diff --git a/src/graphql/types/Post/sorting.ts b/src/graphql/types/Post/sorting.ts new file mode 100644 index 00000000000..e2489fce20f --- /dev/null +++ b/src/graphql/types/Post/sorting.ts @@ -0,0 +1,62 @@ +import { asc, desc, eq, inArray } from "drizzle-orm"; +import { postAttachmentsTable } from "~/src/drizzle/tables/postAttachments"; +import { postsTable } from "~/src/drizzle/tables/posts"; +import { builder } from "~/src/graphql/builder"; +import { Post, type Post as PostType } from "~/src/graphql/types/Post/Post"; +import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError"; + +const GetPostsByOrgInput = builder.inputType("GetPostsByOrgInput", { + fields: (t) => ({ + organizationId: t.string({ required: true }), + sortOrder: t.string({ required: false }), + }), +}); + +builder.queryField("postsByOrganization", (t) => + t.field({ + type: [Post], + args: { + input: t.arg({ type: GetPostsByOrgInput, required: true }), + }, + resolve: async (_parent, { input }, ctx) => { + if (!ctx.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + extensions: { code: "unauthenticated" }, + }); + } + + const { organizationId, sortOrder } = input; + + const orderBy = + sortOrder === "ASC" + ? asc(postsTable.createdAt) + : desc(postsTable.createdAt); + + const posts = await ctx.drizzleClient.query.postsTable.findMany({ + where: eq(postsTable.organizationId, organizationId), + orderBy: [orderBy], + }); + + const postIds = posts.map((post) => post.id); + const attachments = postIds.length + ? await ctx.drizzleClient.query.postAttachmentsTable.findMany({ + where: inArray(postAttachmentsTable.postId, postIds), + }) + : []; + + const attachmentsByPostId = postIds.reduce< + Record + >((acc, postId) => { + acc[postId] = attachments.filter( + (attachment) => attachment.postId === postId, + ); + return acc; + }, {}); + + return posts.map((post) => ({ + ...post, + attachments: attachmentsByPostId[post.id] ?? [], + })) as unknown as PostType[]; + }, + }), +); diff --git a/src/utilities/loadSampleData.ts b/src/utilities/loadSampleData.ts index aa51bddbfb1..622053d1fa2 100644 --- a/src/utilities/loadSampleData.ts +++ b/src/utilities/loadSampleData.ts @@ -10,7 +10,7 @@ import * as schema from "../drizzle/schema"; dotenv.config(); -const dirname: string = path.dirname(fileURLToPath(import.meta.url)); +const dirname = path.dirname(fileURLToPath(import.meta.url)); const queryClient = postgres({ host: process.env.API_POSTGRES_HOST, @@ -86,132 +86,6 @@ async function formatDatabase(): Promise { console.log("Cleared all tables except the specified user\n"); } -/** - * Inserts data into specified tables. - * @param collections - Array of collection/table names to insert data into - * @param options - Options for loading data - */ -async function insertCollections( - collections: string[], - options: LoadOptions = {}, -): Promise { - try { - if (options.format) { - await formatDatabase(); - } - - for (const collection of collections) { - const data = await fs.readFile( - path.resolve(dirname, `../../sample_data/${collection}.json`), - "utf8", - ); - - switch (collection) { - case "users": { - const users = JSON.parse(data).map( - (user: { - createdAt: string | number | Date; - updatedAt: string | number | Date; - }) => ({ - ...user, - createdAt: parseDate(user.createdAt), - updatedAt: parseDate(user.updatedAt), - }), - ) as (typeof schema.usersTable.$inferInsert)[]; - await db.insert(schema.usersTable).values(users); - break; - } - case "organizations": { - const organizations = JSON.parse(data).map( - (org: { - createdAt: string | number | Date; - updatedAt: string | number | Date; - }) => ({ - ...org, - createdAt: parseDate(org.createdAt), - updatedAt: parseDate(org.updatedAt), - }), - ) as (typeof schema.organizationsTable.$inferInsert)[]; - await db.insert(schema.organizationsTable).values(organizations); - - // Add API_ADMINISTRATOR_USER_EMAIL_ADDRESS as administrator of the all organization - const API_ADMINISTRATOR_USER_EMAIL_ADDRESS = - process.env.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; - if (!API_ADMINISTRATOR_USER_EMAIL_ADDRESS) { - console.error( - "\x1b[31m", - "API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not defined in .env file", - ); - return; - } - - const API_ADMINISTRATOR_USER = await db.query.usersTable.findFirst({ - columns: { - id: true, - }, - where: (fields, operators) => - operators.eq( - fields.emailAddress, - API_ADMINISTRATOR_USER_EMAIL_ADDRESS, - ), - }); - if (!API_ADMINISTRATOR_USER) { - console.error( - "\x1b[31m", - "API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not found in users table", - ); - return; - } - - const organizationAdminMembership = organizations.map((org) => ({ - organizationId: org.id, - memberId: API_ADMINISTRATOR_USER.id, - creatorId: API_ADMINISTRATOR_USER.id, - createdAt: new Date(), - role: "administrator", - })) as (typeof schema.organizationMembershipsTable.$inferInsert)[]; - await db - .insert(schema.organizationMembershipsTable) - .values(organizationAdminMembership); - console.log( - "\x1b[35m", - "Added API_ADMINISTRATOR_USER as administrator of the all organization", - ); - break; - } - case "organization_memberships": { - // Add case for organization memberships - const organizationMemberships = JSON.parse(data).map( - (membership: { createdAt: string | number | Date }) => ({ - ...membership, - createdAt: parseDate(membership.createdAt), - }), - ) as (typeof schema.organizationMembershipsTable.$inferInsert)[]; - await db - .insert(schema.organizationMembershipsTable) - .values(organizationMemberships); - break; - } - - default: - console.log("\x1b[31m", `Invalid table name: ${collection}`); - break; - } - - console.log("\x1b[35m", `Added ${collection} table data`); - } - - await checkCountAfterImport(); - await queryClient.end(); - - console.log("\nTables populated successfully"); - } catch (err) { - console.error("\x1b[31m", `Error adding data to tables: ${err}`); - } finally { - process.exit(0); - } -} - /** * Parses a date string and returns a Date object. Returns null if the date is invalid. * @param date - The date string to parse @@ -235,6 +109,7 @@ async function checkCountAfterImport(): Promise { name: "organization_memberships", table: schema.organizationMembershipsTable, }, + { name: "posts", table: schema.postsTable }, ]; console.log("\nRecord Counts After Import:\n"); @@ -267,8 +142,190 @@ ${"|".padEnd(30, "-")}|----------------| } } -const collections = ["users", "organizations", "organization_memberships"]; // Add organization memberships to collections +/** + * Inserts data into specified tables. + * @param collections - Array of collection/table names to insert data into + * @param options - Options for loading data + */ +async function insertCollections( + collections: string[], + options: LoadOptions = {}, +): Promise { + try { + if (options.format) { + await formatDatabase(); + } + + for (const collection of collections) { + // Map collection names to file names + const fileMap: { [key: string]: string } = { + users: "users.json", + organizations: "organizations.json", + organization_memberships: "organization_memberships.json", + posts: "post.json", // Maps to post.json + }; + + const filename = fileMap[collection]; + if (!filename) { + console.error(`No file mapping for collection: ${collection}`); + continue; + } + + console.log(`\nProcessing ${filename}...`); + + try { + const data = await fs.readFile( + path.resolve(dirname, `../../sample_data/${filename}`), + "utf8", + ); + + switch (collection) { + case "users": { + const users = JSON.parse(data).map( + (user: { + createdAt: string | number | Date; + updatedAt: string | number | Date; + }) => ({ + ...user, + createdAt: parseDate(user.createdAt), + updatedAt: parseDate(user.updatedAt), + }), + ) as (typeof schema.usersTable.$inferInsert)[]; + await db.insert(schema.usersTable).values(users); + break; + } + case "organizations": { + const organizations = JSON.parse(data).map( + (org: { + createdAt: string | number | Date; + updatedAt: string | number | Date; + }) => ({ + ...org, + createdAt: parseDate(org.createdAt), + updatedAt: parseDate(org.updatedAt), + }), + ) as (typeof schema.organizationsTable.$inferInsert)[]; + await db.insert(schema.organizationsTable).values(organizations); + + // Add administrator as admin of all organizations + const adminEmail = process.env.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; + if (!adminEmail) { + console.error( + "API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not defined in .env file", + ); + return; + } + + const admin = await db.query.usersTable.findFirst({ + columns: { id: true }, + where: (fields, operators) => + operators.eq(fields.emailAddress, adminEmail), + }); + if (!admin) { + console.error("Administrator user not found in users table"); + return; + } + + const orgAdminMemberships = organizations.map((org) => ({ + organizationId: org.id, + memberId: admin.id, + creatorId: admin.id, + createdAt: new Date(), + role: "administrator", + })) as (typeof schema.organizationMembershipsTable.$inferInsert)[]; + + await db + .insert(schema.organizationMembershipsTable) + .values(orgAdminMemberships); + console.log("Added administrator as admin of all organizations"); + break; + } + case "organization_memberships": { + const memberships = JSON.parse(data).map( + (membership: { createdAt: string | number | Date }) => ({ + ...membership, + createdAt: parseDate(membership.createdAt), + }), + ) as (typeof schema.organizationMembershipsTable.$inferInsert)[]; + await db + .insert(schema.organizationMembershipsTable) + .values(memberships); + break; + } + case "posts": { + console.log("Processing posts..."); + const rawData = JSON.parse(data); + console.log(`Found ${rawData.length} posts to import`); + + const posts = rawData.map( + (post: { + createdAt: string | number | Date; + updatedAt: string | number | Date; + title: string; + text: string; + organizationId: string; + creatorId: string; + }) => { + console.log(`Processing post: ${post.title || "Untitled"}`); + return { + ...post, + caption: post.text, // Map the text field to caption + createdAt: parseDate(post.createdAt), + updatedAt: parseDate(post.updatedAt), + }; + }, + ) as (typeof schema.postsTable.$inferInsert)[]; + + try { + await db.insert(schema.postsTable).values(posts); + console.log(`Successfully inserted ${posts.length} posts`); + } catch (insertError) { + console.error("Error inserting posts:", insertError); + if (posts.length > 0) { + console.log( + "Sample post data:", + JSON.stringify(posts[0], null, 2), + ); + } + throw insertError; + } + break; + } + default: + console.log(`Invalid table name: ${collection}`); + break; + } + + console.log(`Added ${collection} table data`); + } catch (fileError) { + console.error(`Error processing ${filename}:`, fileError); + throw fileError; + } + } + + await checkCountAfterImport(); + await queryClient.end(); + + console.log("\nTables populated successfully"); + } catch (err) { + console.error("Error adding data to tables:", err); + if (err instanceof Error) { + console.error("Error stack:", err.stack); + } + } finally { + process.exit(0); + } +} + +// Define collection names +const collections = [ + "users", + "organizations", + "organization_memberships", + "posts", +]; +// Parse command line arguments const args = process.argv.slice(2); const options: LoadOptions = { format: args.includes("--format") || args.includes("-f"), @@ -281,6 +338,7 @@ if (itemsIndex !== -1 && args[itemsIndex + 1]) { options.items = items ? items.split(",") : undefined; } +// Run the import process (async (): Promise => { await listSampleData(); diff --git a/test/routes/graphql/gql.tada-cache.d.ts b/test/routes/graphql/gql.tada-cache.d.ts index a1463432fff..9350c22239a 100644 --- a/test/routes/graphql/gql.tada-cache.d.ts +++ b/test/routes/graphql/gql.tada-cache.d.ts @@ -44,5 +44,11 @@ declare module 'gql.tada' { TadaDocumentNode<{ deleteOrganization: { id: string; name: string | null; countryCode: "at" | "pg" | "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "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" | "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; } | null; }, { input: { id: string; }; }, void>; "mutation Mutation_deleteOrganizationMembership($input: MutationDeleteOrganizationMembershipInput!) {\n deleteOrganizationMembership(input: $input) {\n id\n name\n countryCode\n }\n}": TadaDocumentNode<{ deleteOrganizationMembership: { id: string; name: string | null; countryCode: "at" | "pg" | "ad" | "ae" | "af" | "ag" | "ai" | "al" | "am" | "ao" | "aq" | "ar" | "as" | "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" | "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; } | null; }, { input: { organizationId: string; memberId: string; }; }, void>; + "\n query tag($input:QueryTagInput!) {\n tag(input: $input) {\n id\n name\n organization {\n id\n }\n createdAt\n }\n}": + TadaDocumentNode<{ tag: { id: string; name: string | null; organization: { id: string; } | null; createdAt: string | null; } | null; }, { input: { id: string; }; }, void>; + "\n mutation CreateTag($input:MutationCreateTagInput!) {\n createTag(input: $input) {\n id\n name\n createdAt\n organization{\n id\n name\n createdAt\n\n }\n }\n }": + TadaDocumentNode<{ createTag: { id: string; name: string | null; createdAt: string | null; organization: { id: string; name: string | null; createdAt: string | null; } | null; } | null; }, { input: { organizationId: string; name: string; folderId?: string | null | undefined; }; }, void>; + "\n query Organization($input: QueryOrganizationInput!, $first: Int!) {\n organization(input: $input) {\n id\n name\n members(first: $first) {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n ": + TadaDocumentNode<{ organization: { id: string; name: string | null; members: { edges: ({ node: { id: string; name: string | null; } | null; } | null)[] | null; } | null; } | null; }, { first: number; input: { id: string; }; }, void>; } }