From 755cdc9c517b88fd29ab85614e479278286ddc6c Mon Sep 17 00:00:00 2001 From: Alexander Petkov Date: Wed, 15 May 2024 17:25:15 +0300 Subject: [PATCH 1/4] fix: Check if sendgrid list exists before attempting to create new one For some reason, campaigns' email list exists on sendgrid, but the data is not stored in the internal notification_list table. This behavior results in Bad Request whenever user attempts to subscripe for email notifications for a particular campaign , as we try to create two lists with the same name. Fix this by checking whether a contact list with the same name, exists before attempting to create a new one. --- apps/api/src/campaign/campaign.service.ts | 14 +++++++++++--- .../notifications.interface.providers.ts | 2 ++ .../notifications.sendgrid.provider.ts | 10 +++++++++- .../providers/notifications.sendgrid.types.ts | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/apps/api/src/campaign/campaign.service.ts b/apps/api/src/campaign/campaign.service.ts index d487e5197..f3413018a 100644 --- a/apps/api/src/campaign/campaign.service.ts +++ b/apps/api/src/campaign/campaign.service.ts @@ -1037,9 +1037,17 @@ export class CampaignService { async createCampaignNotificationList(updated: { title: string; id: string }) { // Generate list in the marketing platform - const listId = await this.marketingNotificationsService.provider.createNewContactList({ - name: updated.title || updated.id, - }) + let listId: string = '' + const lists = await this.marketingNotificationsService.provider.getContactLists() + const campaginEmailLists = lists.body.result + const exists = campaginEmailLists.find((campaign) => campaign.name === updated.title) + if (exists) { + listId = exists.id + } else { + listId = await this.marketingNotificationsService.provider.createNewContactList({ + name: updated.title || updated.id, + }) + } const name = updated.title || '' diff --git a/apps/api/src/notifications/providers/notifications.interface.providers.ts b/apps/api/src/notifications/providers/notifications.interface.providers.ts index 47d4e5a34..4e6e6d28f 100644 --- a/apps/api/src/notifications/providers/notifications.interface.providers.ts +++ b/apps/api/src/notifications/providers/notifications.interface.providers.ts @@ -19,6 +19,7 @@ type NotificationsInterfaceParams = { RemoveFromUnsubscribedRes: unknown AddToUnsubscribedRes: unknown SendNotificationRes: unknown + contactListsRes: unknown } export abstract class NotificationsProviderInterface< @@ -35,4 +36,5 @@ export abstract class NotificationsProviderInterface< data: T['RemoveFromUnsubscribedParams'], ): Promise abstract sendNotification(data: T['SendNotificationParams']): Promise + abstract getContactLists(): Promise } diff --git a/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts b/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts index 6e0ee1185..5778209c5 100644 --- a/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts +++ b/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import sgClient from '@sendgrid/client' import { NotificationsProviderInterface } from './notifications.interface.providers' -import { SendGridParams } from './notifications.sendgrid.types' +import { ContactListRes, SGClientResponse, SendGridParams } from './notifications.sendgrid.types' import { ClientRequest } from '@sendgrid/client/src/request' import { DateTime } from 'luxon' @@ -25,6 +25,14 @@ export class SendGridNotificationsProvider } } + async getContactLists() { + const request = { + url: '/v3/marketing/lists', + method: 'GET', + } as ClientRequest + const [response] = await sgClient.request(request) + return response as SGClientResponse + } async createNewContactList(data: SendGridParams['CreateListParams']) { const request = { url: `/v3/marketing/lists`, diff --git a/apps/api/src/notifications/providers/notifications.sendgrid.types.ts b/apps/api/src/notifications/providers/notifications.sendgrid.types.ts index 8c907821e..e178a7c53 100644 --- a/apps/api/src/notifications/providers/notifications.sendgrid.types.ts +++ b/apps/api/src/notifications/providers/notifications.sendgrid.types.ts @@ -1,4 +1,8 @@ +import { ClientResponse } from '@sendgrid/mail' import { MarketingTemplateHTMLFields } from '../marketing_templates/template.type' +import Response from '@sendgrid/helpers/classes/response' + +export type SGClientResponse = Response export type SendGridParams = { // Parameters @@ -22,6 +26,7 @@ export type SendGridParams = { RemoveFromUnsubscribedRes: unknown AddToUnsubscribedRes: unknown SendNotificationRes: unknown + contactListsRes: SGClientResponse // Implementation specific ContactData: ContactData @@ -81,3 +86,15 @@ type SendNotificationParams = { type GetContactsInfoRes = { [key: string]: { contact: { id: string; [key: string]: unknown; list_ids: string[] } } } + +type SendGridContactList = { + id: string + name: string + contact_count: number + _metadata: object +} + +export type ContactListRes = { + result: SendGridContactList[] + _metadata: object +} From a81f458cbdd3259ec1faa91d7c42d8c4950a6164 Mon Sep 17 00:00:00 2001 From: Alexander Petkov Date: Wed, 15 May 2024 17:41:14 +0300 Subject: [PATCH 2/4] chore: Adjus tests to latest change --- apps/api/src/campaign/campaign.service.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/api/src/campaign/campaign.service.spec.ts b/apps/api/src/campaign/campaign.service.spec.ts index 01633a49b..c7969f9e7 100644 --- a/apps/api/src/campaign/campaign.service.spec.ts +++ b/apps/api/src/campaign/campaign.service.spec.ts @@ -14,10 +14,11 @@ import { NotificationService } from '../sockets/notifications/notification.servi import { SendGridNotificationsProvider } from '../notifications/providers/notifications.sendgrid.provider' import { EmailService } from '../email/email.service' import { MarketingNotificationsService } from '../notifications/notifications.service' +import { SendGridParams } from '../notifications/providers/notifications.sendgrid.types' describe('CampaignService', () => { let service: CampaignService - let marketing: NotificationsProviderInterface + let marketing: NotificationsProviderInterface const mockCreateCampaign = { slug: 'test-slug', @@ -121,6 +122,14 @@ describe('CampaignService', () => { jest.spyOn(marketing, 'createNewContactList').mockImplementation(async () => 'list-id') jest.spyOn(marketing, 'updateContactList').mockImplementation(async () => '') + jest.spyOn(marketing, 'getContactLists').mockResolvedValue({ + statusCode: 200, + headers: '', + body: { + result: [], + _metadata: {}, + }, + }) jest.spyOn(service, 'createCampaignNotificationList') expect(await service.update(mockUpdateCampaign.id, updateData, mockCampaign)).toEqual( @@ -133,6 +142,7 @@ describe('CampaignService', () => { include: { campaignType: { select: { name: true, slug: true, category: true } } }, }) expect(service.createCampaignNotificationList).toHaveBeenCalledWith(updatedCampaign) + expect(marketing.createNewContactList).toHaveBeenCalledWith({ name: updatedCampaign.title, }) From 245706832d963e5a0214791fbc66b7b74a36ef9b Mon Sep 17 00:00:00 2001 From: Alexander Petkov Date: Wed, 15 May 2024 17:45:49 +0300 Subject: [PATCH 3/4] fix: Linter error --- apps/api/src/campaign/campaign.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/campaign/campaign.service.ts b/apps/api/src/campaign/campaign.service.ts index f3413018a..98f63db74 100644 --- a/apps/api/src/campaign/campaign.service.ts +++ b/apps/api/src/campaign/campaign.service.ts @@ -1037,7 +1037,7 @@ export class CampaignService { async createCampaignNotificationList(updated: { title: string; id: string }) { // Generate list in the marketing platform - let listId: string = '' + let listId: string const lists = await this.marketingNotificationsService.provider.getContactLists() const campaginEmailLists = lists.body.result const exists = campaginEmailLists.find((campaign) => campaign.name === updated.title) From 2ea3c8efd68f6f6b176ff8ded0d20dc4c4e3b57c Mon Sep 17 00:00:00 2001 From: Aleksandar Petkov Date: Mon, 15 Jul 2024 12:49:47 +0300 Subject: [PATCH 4/4] fix: typo Co-authored-by: Ivan Milchev --- apps/api/src/campaign/campaign.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/campaign/campaign.service.ts b/apps/api/src/campaign/campaign.service.ts index 98f63db74..da8518a93 100644 --- a/apps/api/src/campaign/campaign.service.ts +++ b/apps/api/src/campaign/campaign.service.ts @@ -1040,7 +1040,7 @@ export class CampaignService { let listId: string const lists = await this.marketingNotificationsService.provider.getContactLists() const campaginEmailLists = lists.body.result - const exists = campaginEmailLists.find((campaign) => campaign.name === updated.title) + const exists = campaignEmailLists.find((campaign) => campaign.name === updated.title) if (exists) { listId = exists.id } else {