diff --git a/packages/api/src/brevo-api/brevo-api-folders.service.ts b/packages/api/src/brevo-api/brevo-api-folders.service.ts new file mode 100644 index 00000000..8be808d9 --- /dev/null +++ b/packages/api/src/brevo-api/brevo-api-folders.service.ts @@ -0,0 +1,34 @@ +import * as Brevo from "@getbrevo/brevo"; +import { Inject, Injectable } from "@nestjs/common"; +import { EmailCampaignScopeInterface } from "src/types"; + +import { BrevoModuleConfig } from "../config/brevo-module.config"; +import { BREVO_MODULE_CONFIG } from "../config/brevo-module.constants"; +import { BrevoApiFolder } from "./dto/brevo-api-folder"; + +@Injectable() +export class BrevoApiFoldersService { + private readonly contactsApi: Brevo.ContactsApi; + + constructor(@Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig) { + this.contactsApi = new Brevo.ContactsApi(); + } + + public async getFolders(scope: EmailCampaignScopeInterface): Promise | undefined> { + const apiKey = this.config.brevo.resolveConfig(scope).apiKey; + this.contactsApi.setApiKey(Brevo.ContactsApiApiKeys.apiKey, apiKey); + + // Limit set by Brevo + const limit = 50; + + const offset = 0; + + const { response, body } = await this.contactsApi.getFolders(limit, offset); + + if (response.statusCode !== 200) { + throw new Error("Failed to get folders"); + } + + return body.folders as BrevoApiFolder[]; + } +} diff --git a/packages/api/src/brevo-api/brevo-api.module.ts b/packages/api/src/brevo-api/brevo-api.module.ts index 5ee4542b..85a53fee 100644 --- a/packages/api/src/brevo-api/brevo-api.module.ts +++ b/packages/api/src/brevo-api/brevo-api.module.ts @@ -5,12 +5,13 @@ import { Module } from "@nestjs/common"; import { ConfigModule } from "../config/config.module"; import { BrevoApiCampaignsService } from "./brevo-api-campaigns.service"; import { BrevoApiContactsService } from "./brevo-api-contact.service"; +import { BrevoApiFoldersService } from "./brevo-api-folders.service"; import { BrevoApiSenderService } from "./brevo-api-sender.service"; import { BrevoTransactionalMailsService } from "./brevo-api-transactional-mails.service"; @Module({ imports: [ConfigModule, CacheModule.register({ ttl: 1000 * 60 }), MikroOrmModule.forFeature(["BrevoConfig"])], - providers: [BrevoApiContactsService, BrevoApiCampaignsService, BrevoTransactionalMailsService, BrevoApiSenderService], - exports: [BrevoApiContactsService, BrevoApiCampaignsService, BrevoTransactionalMailsService, BrevoApiSenderService], + providers: [BrevoApiContactsService, BrevoApiCampaignsService, BrevoTransactionalMailsService, BrevoApiSenderService, BrevoApiFoldersService], + exports: [BrevoApiContactsService, BrevoApiCampaignsService, BrevoTransactionalMailsService, BrevoApiSenderService, BrevoApiFoldersService], }) export class BrevoApiModule {} diff --git a/packages/api/src/brevo-api/dto/brevo-api-folder.ts b/packages/api/src/brevo-api/dto/brevo-api-folder.ts new file mode 100644 index 00000000..5815eb10 --- /dev/null +++ b/packages/api/src/brevo-api/dto/brevo-api-folder.ts @@ -0,0 +1,10 @@ +import { Field, ID, ObjectType } from "@nestjs/graphql"; + +@ObjectType() +export class BrevoApiFolder { + @Field(() => ID) + id: number; + + @Field(() => String) + name: string; +} diff --git a/packages/api/src/brevo-config/brevo-config.resolver.ts b/packages/api/src/brevo-config/brevo-config.resolver.ts index c04ab617..c303be7d 100644 --- a/packages/api/src/brevo-config/brevo-config.resolver.ts +++ b/packages/api/src/brevo-config/brevo-config.resolver.ts @@ -4,6 +4,7 @@ import { InjectRepository } from "@mikro-orm/nestjs"; import { Type } from "@nestjs/common"; import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql"; +import { BrevoApiFoldersService } from "../brevo-api/brevo-api-folders.service"; import { BrevoApiSenderService } from "../brevo-api/brevo-api-sender.service"; import { BrevoTransactionalMailsService } from "../brevo-api/brevo-api-transactional-mails.service"; import { BrevoApiEmailTemplate } from "../brevo-api/dto/brevo-api-email-templates-list"; @@ -26,6 +27,7 @@ export function createBrevoConfigResolver({ constructor( private readonly entityManager: EntityManager, private readonly brevoSenderApiService: BrevoApiSenderService, + private readonly brevoFolderIdService: BrevoApiFoldersService, private readonly brevoTransactionalEmailsApiService: BrevoTransactionalMailsService, @InjectRepository(BrevoConfig) private readonly repository: EntityRepository, ) {} @@ -50,6 +52,16 @@ export function createBrevoConfigResolver({ return false; } + private async isValidFolderId({ folderId }: { folderId: number }): Promise { + const folders = await this.brevoFolderIdService.getFolders(Scope); + + if (folders && folders.some((folder) => folder.id === folderId)) { + return true; + } + + return false; + } + @RequiredPermission(["brevo-newsletter-config"], { skipScopeCheck: true }) @Query(() => [BrevoApiSender], { nullable: true }) async senders( @@ -91,6 +103,10 @@ export function createBrevoConfigResolver({ throw new Error("Template ID is not valid. "); } + if (!(await this.isValidFolderId({ folderId: input.folderId }))) { + throw new Error("Folder ID is not valid. "); + } + const brevoConfig = this.repository.create({ ...input, scope, @@ -121,6 +137,12 @@ export function createBrevoConfigResolver({ } } + if (input.folderId) { + if (!(await this.isValidFolderId({ folderId: input.folderId }))) { + throw new Error("Folder ID is not valid. "); + } + } + if (lastUpdatedAt) { validateNotModified(brevoConfig, lastUpdatedAt); }