From d21db92dccebc86de398fda7fa1a3d92130e8b40 Mon Sep 17 00:00:00 2001 From: juliawegmayr <109900447+juliawegmayr@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:25:53 +0200 Subject: [PATCH] COM-687 Add automatic deletion process (#59) * add job for deleting blacklistedContacts * add changeset * fix prettier error * refactor findNonMainTargetGroups to findMainTargetGroups including where parameter * add where parameter to brevo-contacts-services functions * use findMainTargetGroups function to get scopes for deleting contacts * fix prettier problems * fix prettier problems * add @CreateRequestContext to execute function * fix pipeline * correct scope by using dynamic values * rename variable numberOfBlacklistedContacts to hasMoreContacts * correct condition to check if more contacts are avaiable * rename findMainTargetGroups to findTargetGroups * correct type of where in findTargetGroups * use scope directly * fix lint error * adapt changeset to only include information that is relevant for the application * remove scope from findTargetGroups and pass it in the prop instead * correct hasMoreContacts * rename DeleteUnsubscribedContactsConsole to DeleteUnsubscribedBrevoContactsConsole --------- Co-authored-by: Julia Wegmayr --- .changeset/cold-clouds-hang.md | 5 ++ .../brevo-api/brevo-api-contact.service.ts | 17 +++++++ .../brevo-contact/brevo-contact.console.ts | 49 +++++++++++++++++++ .../src/brevo-contact/brevo-contact.module.ts | 9 +++- .../brevo-contact/brevo-contacts.service.ts | 6 ++- .../src/target-group/target-groups.service.ts | 16 ++---- 6 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 .changeset/cold-clouds-hang.md create mode 100644 packages/api/src/brevo-contact/brevo-contact.console.ts diff --git a/.changeset/cold-clouds-hang.md b/.changeset/cold-clouds-hang.md new file mode 100644 index 00000000..96f6eb97 --- /dev/null +++ b/.changeset/cold-clouds-hang.md @@ -0,0 +1,5 @@ +--- +"@comet/brevo-api": minor +--- + +Add `DeleteUnsubscribedBrevoContactsConsole` job to enable the deletion of blocklisted contacts. This job can be utilized as a cronjob to periodically clean up the blocklisted contacts. diff --git a/packages/api/src/brevo-api/brevo-api-contact.service.ts b/packages/api/src/brevo-api/brevo-api-contact.service.ts index b6fe47ac..d88963f0 100644 --- a/packages/api/src/brevo-api/brevo-api-contact.service.ts +++ b/packages/api/src/brevo-api/brevo-api-contact.service.ts @@ -114,6 +114,23 @@ export class BrevoApiContactsService { return [data.body.contacts, data.body.count]; } + public async findContacts(limit: number, offset: number, scope: EmailCampaignScopeInterface): Promise { + const data = await this.getContactsApi(scope).getContacts(limit, offset); + + return data.body.contacts; + } + + public async deleteContacts(contacts: BrevoContactInterface[], scope: EmailCampaignScopeInterface): Promise { + for (const contact of contacts) { + const idAsString = contact.id.toString(); + const response = await this.getContactsApi(scope).deleteContact(idAsString); + if (response.response.statusCode !== 204) { + return false; + } + } + return true; + } + public async blacklistMultipleContacts(emails: string[], scope: EmailCampaignScopeInterface): Promise { const blacklistedContacts = emails.map((email) => ({ email, emailBlacklisted: true })); diff --git a/packages/api/src/brevo-contact/brevo-contact.console.ts b/packages/api/src/brevo-contact/brevo-contact.console.ts new file mode 100644 index 00000000..e41f2472 --- /dev/null +++ b/packages/api/src/brevo-contact/brevo-contact.console.ts @@ -0,0 +1,49 @@ +import { CreateRequestContext, MikroORM } from "@mikro-orm/core"; +import { Injectable } from "@nestjs/common"; +import { Command, Console } from "nestjs-console"; + +import { BrevoApiContactsService } from "../brevo-api/brevo-api-contact.service"; +import { TargetGroupsService } from "../target-group/target-groups.service"; + +@Injectable() +@Console() +export class DeleteUnsubscribedBrevoContactsConsole { + constructor( + private readonly brevoApiContactsService: BrevoApiContactsService, + private readonly targetGroupsService: TargetGroupsService, + private readonly orm: MikroORM, + ) {} + + @Command({ + command: "delete-unsubscribed-brevo-contacts", + description: "deletes unsubscribed contacts", + }) + @CreateRequestContext() + async execute(): Promise { + const offset = 0; + const limit = 50; + const where = { isMainList: true }; + + const [targetGroups] = await this.targetGroupsService.findTargetGroups({ offset, limit, where }); + + for (const targetGroup of targetGroups) { + let hasMoreContacts = false; + let offset = 0; + + do { + const contacts = await this.brevoApiContactsService.findContacts(limit, offset, { + scope: targetGroup.scope, + }); + + const blacklistedContacts = contacts.filter((contact) => contact.emailBlacklisted === true); + + if (blacklistedContacts.length > 0) { + await this.brevoApiContactsService.deleteContacts(blacklistedContacts, { scope: targetGroup.scope }); + } + + hasMoreContacts = !(contacts.length < limit); + offset += limit; + } while (hasMoreContacts); + } + } +} diff --git a/packages/api/src/brevo-contact/brevo-contact.module.ts b/packages/api/src/brevo-contact/brevo-contact.module.ts index 4e59b09f..916aa655 100644 --- a/packages/api/src/brevo-contact/brevo-contact.module.ts +++ b/packages/api/src/brevo-contact/brevo-contact.module.ts @@ -5,6 +5,7 @@ import { BrevoApiModule } from "../brevo-api/brevo-api.module"; import { ConfigModule } from "../config/config.module"; import { TargetGroupInterface } from "../target-group/entity/target-group-entity.factory"; import { BrevoContactAttributesInterface, EmailCampaignScopeInterface } from "../types"; +import { DeleteUnsubscribedBrevoContactsConsole } from "./brevo-contact.console"; import { createBrevoContactResolver } from "./brevo-contact.resolver"; import { BrevoContactsService } from "./brevo-contacts.service"; import { BrevoContactFactory } from "./dto/brevo-contact.factory"; @@ -36,7 +37,13 @@ export class BrevoContactModule { return { module: BrevoContactModule, imports: [BrevoApiModule, ConfigModule, MikroOrmModule.forFeature([TargetGroup])], - providers: [BrevoContactsService, BrevoContactResolver, EcgRtrListService, IsValidRedirectURLConstraint], + providers: [ + BrevoContactsService, + BrevoContactResolver, + EcgRtrListService, + IsValidRedirectURLConstraint, + DeleteUnsubscribedBrevoContactsConsole, + ], exports: [BrevoContactsService], }; } diff --git a/packages/api/src/brevo-contact/brevo-contacts.service.ts b/packages/api/src/brevo-contact/brevo-contacts.service.ts index 12ff9a37..ec42c18b 100644 --- a/packages/api/src/brevo-contact/brevo-contacts.service.ts +++ b/packages/api/src/brevo-contact/brevo-contacts.service.ts @@ -55,9 +55,10 @@ export class BrevoContactsService { let totalCount = 0; const targetGroupIds: number[] = []; const limit = 50; + const where = { isMainList: false, scope }; do { - const [targetGroups, totalContactLists] = await this.targetGroupService.findNonMainTargetGroups({ scope, offset, limit }); + const [targetGroups, totalContactLists] = await this.targetGroupService.findTargetGroups({ offset, limit, where }); totalCount = totalContactLists; offset += targetGroups.length; @@ -102,9 +103,10 @@ export class BrevoContactsService { let totalCount = 0; const targetGroupIds: number[] = []; const limit = 50; + const where = { isMainList: false, scope }; do { - const [targetGroups, totalContactLists] = await this.targetGroupService.findNonMainTargetGroups({ scope, offset, limit }); + const [targetGroups, totalContactLists] = await this.targetGroupService.findTargetGroups({ offset, limit, where }); totalCount = totalContactLists; offset += targetGroups.length; diff --git a/packages/api/src/target-group/target-groups.service.ts b/packages/api/src/target-group/target-groups.service.ts index a30379e2..30cc5ce9 100644 --- a/packages/api/src/target-group/target-groups.service.ts +++ b/packages/api/src/target-group/target-groups.service.ts @@ -1,5 +1,5 @@ import { filtersToMikroOrmQuery, searchToMikroOrmQuery } from "@comet/cms-api"; -import { EntityManager, EntityRepository, ObjectQuery } from "@mikro-orm/core"; +import { EntityManager, EntityRepository, FilterQuery, ObjectQuery } from "@mikro-orm/core"; import { InjectRepository } from "@mikro-orm/nestjs"; import { Injectable } from "@nestjs/common"; @@ -106,22 +106,16 @@ export class TargetGroupsService { return true; } - async findNonMainTargetGroups({ - scope, + async findTargetGroups({ offset, limit, + where, }: { - scope?: EmailCampaignScopeInterface; offset: number; limit: number; + where: FilterQuery; }): Promise<[TargetGroupInterface[], number]> { - const [targetGroups, totalContactLists] = await this.repository.findAndCount( - { - isMainList: false, - ...(scope ? { scope } : {}), - }, - { limit, offset }, - ); + const [targetGroups, totalContactLists] = await this.repository.findAndCount(where, { offset, limit }); return [targetGroups, totalContactLists]; }