From 4cc5a986285bec21e84e78d98d6663c045ffbad3 Mon Sep 17 00:00:00 2001 From: Jacob Edley Date: Thu, 18 Sep 2025 17:04:47 -0500 Subject: [PATCH 1/3] deduplicate subscriptions in GET /subscription --- .../subscription/subscription-router.test.ts | 45 ++++++++++++++++--- .../subscription/subscription-router.ts | 21 ++++++++- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/services/subscription/subscription-router.test.ts b/src/services/subscription/subscription-router.test.ts index 61705212..d8b9121b 100644 --- a/src/services/subscription/subscription-router.test.ts +++ b/src/services/subscription/subscription-router.test.ts @@ -166,7 +166,7 @@ describe("GET /subscription/", () => { expect(response.body).toEqual([]); }); - it("should return all subscriptions", async () => { + it("should return aggregated subscriptions by mailing list", async () => { await SupabaseDB.SUBSCRIPTIONS.insert([ { userId: USER_ID_1, mailingList: VALID_mailingList }, { userId: USER_ID_2, mailingList: VALID_mailingList }, @@ -175,14 +175,47 @@ describe("GET /subscription/", () => { StatusCodes.OK ); + expect(response.body.length).toBe(1); + expect(response.body[0]).toEqual({ + mailingList: VALID_mailingList, + userIds: expect.arrayContaining([USER_ID_1, USER_ID_2]), + }); + expect(response.body[0].userIds.length).toBe(2); + }); + + it("should aggregate multiple mailing lists correctly", async () => { + const mailingList2 = "newsletter"; + await SupabaseDB.SUBSCRIPTIONS.insert([ + { userId: USER_ID_1, mailingList: VALID_mailingList }, + { userId: USER_ID_2, mailingList: VALID_mailingList }, + { userId: USER_ID_1, mailingList: mailingList2 }, + ]).throwOnError(); + + const response = await getAsAdmin("/subscription/").expect( + StatusCodes.OK + ); + expect(response.body.length).toBe(2); - expect(response.body).toEqual( - expect.arrayContaining([ - { userId: USER_ID_1, mailingList: VALID_mailingList }, - { userId: USER_ID_2, mailingList: VALID_mailingList }, - ]) + const validMailingListEntry = response.body.find( + (item: { mailingList: string; userIds: string[] }) => + item.mailingList === VALID_mailingList + ); + const newsletterEntry = response.body.find( + (item: { mailingList: string; userIds: string[] }) => + item.mailingList === mailingList2 ); + + expect(validMailingListEntry).toEqual({ + mailingList: VALID_mailingList, + userIds: expect.arrayContaining([USER_ID_1, USER_ID_2]), + }); + expect(validMailingListEntry.userIds.length).toBe(2); + + expect(newsletterEntry).toEqual({ + mailingList: mailingList2, + userIds: [USER_ID_1], + }); }); }); diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index 2b01755e..29cc8108 100644 --- a/src/services/subscription/subscription-router.ts +++ b/src/services/subscription/subscription-router.ts @@ -64,7 +64,26 @@ subscriptionRouter.get( const { data: subscriptions } = await SupabaseDB.SUBSCRIPTIONS.select("*").throwOnError(); - return res.status(StatusCodes.OK).json(subscriptions); + const aggregated = + subscriptions?.reduce( + (acc, subscription) => { + const existing = acc.find( + (item) => item.mailingList === subscription.mailingList + ); + if (existing) { + existing.userIds.push(subscription.userId); + } else { + acc.push({ + mailingList: subscription.mailingList, + userIds: [subscription.userId], + }); + } + return acc; + }, + [] as { mailingList: string; userIds: string[] }[] + ) || []; + + return res.status(StatusCodes.OK).json(aggregated); } ); From 68287ffe05beac0ac9b7d0d96191306f2e8114b5 Mon Sep 17 00:00:00 2001 From: Jacob Edley Date: Thu, 18 Sep 2025 17:35:54 -0500 Subject: [PATCH 2/3] Revert "deduplicate subscriptions in GET /subscription" This reverts commit 4cc5a986285bec21e84e78d98d6663c045ffbad3. --- .../subscription/subscription-router.test.ts | 45 +++---------------- .../subscription/subscription-router.ts | 21 +-------- 2 files changed, 7 insertions(+), 59 deletions(-) diff --git a/src/services/subscription/subscription-router.test.ts b/src/services/subscription/subscription-router.test.ts index d8b9121b..61705212 100644 --- a/src/services/subscription/subscription-router.test.ts +++ b/src/services/subscription/subscription-router.test.ts @@ -166,7 +166,7 @@ describe("GET /subscription/", () => { expect(response.body).toEqual([]); }); - it("should return aggregated subscriptions by mailing list", async () => { + it("should return all subscriptions", async () => { await SupabaseDB.SUBSCRIPTIONS.insert([ { userId: USER_ID_1, mailingList: VALID_mailingList }, { userId: USER_ID_2, mailingList: VALID_mailingList }, @@ -175,47 +175,14 @@ describe("GET /subscription/", () => { StatusCodes.OK ); - expect(response.body.length).toBe(1); - expect(response.body[0]).toEqual({ - mailingList: VALID_mailingList, - userIds: expect.arrayContaining([USER_ID_1, USER_ID_2]), - }); - expect(response.body[0].userIds.length).toBe(2); - }); - - it("should aggregate multiple mailing lists correctly", async () => { - const mailingList2 = "newsletter"; - await SupabaseDB.SUBSCRIPTIONS.insert([ - { userId: USER_ID_1, mailingList: VALID_mailingList }, - { userId: USER_ID_2, mailingList: VALID_mailingList }, - { userId: USER_ID_1, mailingList: mailingList2 }, - ]).throwOnError(); - - const response = await getAsAdmin("/subscription/").expect( - StatusCodes.OK - ); - expect(response.body.length).toBe(2); - const validMailingListEntry = response.body.find( - (item: { mailingList: string; userIds: string[] }) => - item.mailingList === VALID_mailingList - ); - const newsletterEntry = response.body.find( - (item: { mailingList: string; userIds: string[] }) => - item.mailingList === mailingList2 + expect(response.body).toEqual( + expect.arrayContaining([ + { userId: USER_ID_1, mailingList: VALID_mailingList }, + { userId: USER_ID_2, mailingList: VALID_mailingList }, + ]) ); - - expect(validMailingListEntry).toEqual({ - mailingList: VALID_mailingList, - userIds: expect.arrayContaining([USER_ID_1, USER_ID_2]), - }); - expect(validMailingListEntry.userIds.length).toBe(2); - - expect(newsletterEntry).toEqual({ - mailingList: mailingList2, - userIds: [USER_ID_1], - }); }); }); diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index 29cc8108..2b01755e 100644 --- a/src/services/subscription/subscription-router.ts +++ b/src/services/subscription/subscription-router.ts @@ -64,26 +64,7 @@ subscriptionRouter.get( const { data: subscriptions } = await SupabaseDB.SUBSCRIPTIONS.select("*").throwOnError(); - const aggregated = - subscriptions?.reduce( - (acc, subscription) => { - const existing = acc.find( - (item) => item.mailingList === subscription.mailingList - ); - if (existing) { - existing.userIds.push(subscription.userId); - } else { - acc.push({ - mailingList: subscription.mailingList, - userIds: [subscription.userId], - }); - } - return acc; - }, - [] as { mailingList: string; userIds: string[] }[] - ) || []; - - return res.status(StatusCodes.OK).json(aggregated); + return res.status(StatusCodes.OK).json(subscriptions); } ); From 6bc1327129269bcc4b5ccfd2e439208229eb7da5 Mon Sep 17 00:00:00 2001 From: Jacob Edley Date: Thu, 18 Sep 2025 17:53:31 -0500 Subject: [PATCH 3/3] add GET /lists route --- .../subscription/subscription-router.test.ts | 39 +++++++++++++++++++ .../subscription/subscription-router.ts | 15 +++++++ 2 files changed, 54 insertions(+) diff --git a/src/services/subscription/subscription-router.test.ts b/src/services/subscription/subscription-router.test.ts index 61705212..d54958cf 100644 --- a/src/services/subscription/subscription-router.test.ts +++ b/src/services/subscription/subscription-router.test.ts @@ -256,6 +256,45 @@ describe("POST /subscription/send-email/single", () => { }); }); +describe("GET /subscription/lists", () => { + it("should return an empty array when no subscriptions exist", async () => { + const response = await getAsAdmin("/subscription/lists").expect( + StatusCodes.OK + ); + expect(response.body).toEqual([]); + }); + + it("should return unique mailing lists", async () => { + await SupabaseDB.SUBSCRIPTIONS.insert([ + { userId: USER_ID_1, mailingList: VALID_mailingList }, + { userId: USER_ID_2, mailingList: VALID_mailingList }, // duplicate mailing list + { userId: USER_ID_1, mailingList: "newsletter" }, + ]).throwOnError(); + + const response = await getAsAdmin("/subscription/lists").expect( + StatusCodes.OK + ); + + expect(response.body).toHaveLength(2); + expect(response.body).toEqual( + expect.arrayContaining([VALID_mailingList, "newsletter"]) + ); + }); + + it("should return a single unique mailing list when all subscriptions are for the same list", async () => { + await SupabaseDB.SUBSCRIPTIONS.insert([ + { userId: USER_ID_1, mailingList: VALID_mailingList }, + { userId: USER_ID_2, mailingList: VALID_mailingList }, + ]).throwOnError(); + + const response = await getAsAdmin("/subscription/lists").expect( + StatusCodes.OK + ); + + expect(response.body).toEqual([VALID_mailingList]); + }); +}); + describe("GET /subscription/:mailingList", () => { it("should return the list of subscribers for an existing mailing list", async () => { const emails = ["user1@test.com", "user2@test.com"]; diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts index 2b01755e..fcc7fd1d 100644 --- a/src/services/subscription/subscription-router.ts +++ b/src/services/subscription/subscription-router.ts @@ -68,6 +68,21 @@ subscriptionRouter.get( } ); +subscriptionRouter.get( + "/lists", + RoleChecker([Role.Enum.ADMIN]), + async (req, res) => { + const { data: subscriptions } = + await SupabaseDB.SUBSCRIPTIONS.select("mailingList").throwOnError(); + + const uniqueMailingLists = [ + ...new Set(subscriptions?.map((sub) => sub.mailingList) || []), + ]; + + return res.status(StatusCodes.OK).json(uniqueMailingLists); + } +); + // Send an email to a mailing list // API body: {String} mailingList The list to send the email to, {String} subject The subject line of the email, {String} htmlBody The HTML content of the email. subscriptionRouter.post(