Skip to content

Commit

Permalink
add public api route to subscribe to brevo
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelblum committed Jul 11, 2024
1 parent 23371d5 commit ef65801
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/cool-walls-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@comet/brevo-api": major
---

Add a new public route that allows subscribing to the Brevo Module. By default, this route is disabled and can be enabled in the Brevo module configuration via `enablePublicApiSubscriptionRoute: true`. This feature allows external applications to create a newsletter form and subscribe via REST to the Brevo module.
1 change: 1 addition & 0 deletions demo/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export class AppModule {
},
},
},
enablePublicApiSubscriptionRoute: true,
}),
],
};
Expand Down
56 changes: 56 additions & 0 deletions packages/api/src/brevo-contact/brevo-contact.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { DisableGlobalGuard } from "@comet/cms-api";
import { Body, Controller, Post, Type as NestType } from "@nestjs/common";
import { Type } from "class-transformer";
import { IsEmail, IsNotEmpty, IsUrl, Validate, ValidateNested } from "class-validator";
import { BrevoContactAttributesInterface } from "src/types";

import { EmailCampaignScopeInterface } from "../types";
import { BrevoContactsService } from "./brevo-contacts.service";
import { SubscribeResponse } from "./dto/subscribe-response.enum";
import { IsValidRedirectURLConstraint } from "./validator/redirect-url.validator";

export function createBrevoContactController({
BrevoContactAttributes,
Scope,
}: {
BrevoContactAttributes?: NestType<BrevoContactAttributesInterface>;
Scope: NestType<EmailCampaignScopeInterface>;
}): NestType<unknown> {
class SubscribeInputBase {
@IsEmail()
email: string;

@IsUrl({ require_tld: process.env.NODE_ENV === "production" })
@Validate(IsValidRedirectURLConstraint)
redirectionUrl: string;

@IsNotEmpty()
@Type(() => Scope)
@ValidateNested()
scope: typeof Scope;
}

class SubscribeInputWithAttributes extends SubscribeInputBase {
@IsNotEmpty()
@Type(typeof BrevoContactAttributes !== "undefined" ? () => BrevoContactAttributes : undefined)
@ValidateNested()
attributes: typeof BrevoContactAttributes;
}

class SubscribeInput extends (typeof BrevoContactAttributes !== "undefined" ? SubscribeInputWithAttributes : SubscribeInputBase) {}

@Controller("brevo-contacts")
class BrevoContactsController {
constructor(private readonly brevoContactsService: BrevoContactsService) {}

@DisableGlobalGuard()
@Post(`/subscribe`)
async subscribe(@Body() data: SubscribeInput): Promise<SubscribeResponse> {
const { scope, ...input } = data;

return this.brevoContactsService.subscribeBrevoContact(input, data.scope);
}
}

return BrevoContactsController;
}
12 changes: 11 additions & 1 deletion packages/api/src/brevo-contact/brevo-contact.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { createBrevoContactController } from "./brevo-contact.controller";
import { createBrevoContactResolver } from "./brevo-contact.resolver";
import { BrevoContactsService } from "./brevo-contacts.service";
import { BrevoContactFactory } from "./dto/brevo-contact.factory";
Expand All @@ -17,11 +18,12 @@ interface BrevoContactModuleConfig {
BrevoContactAttributes?: Type<BrevoContactAttributesInterface>;
Scope: Type<EmailCampaignScopeInterface>;
TargetGroup: Type<TargetGroupInterface>;
enablePublicApiSubscriptionRoute?: boolean;
}

@Module({})
export class BrevoContactModule {
static register({ BrevoContactAttributes, Scope, TargetGroup }: BrevoContactModuleConfig): DynamicModule {
static register({ BrevoContactAttributes, Scope, TargetGroup, enablePublicApiSubscriptionRoute }: BrevoContactModuleConfig): DynamicModule {
const BrevoContact = BrevoContactFactory.create({ BrevoContactAttributes });
const BrevoContactSubscribeInput = SubscribeInputFactory.create({ BrevoContactAttributes, Scope });
const [BrevoContactInput, BrevoContactUpdateInput] = BrevoContactInputFactory.create({ BrevoContactAttributes });
Expand All @@ -33,10 +35,18 @@ export class BrevoContactModule {
BrevoContactUpdateInput,
});

const controllers = [];
if (enablePublicApiSubscriptionRoute) {
const BrevoContactController = createBrevoContactController({ BrevoContactAttributes, Scope });
controllers.push(BrevoContactController);
}

return {
module: BrevoContactModule,
imports: [BrevoApiModule, ConfigModule, MikroOrmModule.forFeature([TargetGroup])],
providers: [BrevoContactsService, BrevoContactResolver, EcgRtrListService, IsValidRedirectURLConstraint],
controllers,
exports: [BrevoContactsService],
};
}
}
16 changes: 1 addition & 15 deletions packages/api/src/brevo-contact/brevo-contact.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,21 +173,7 @@ export function createBrevoContactResolver({
@Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope))
scope: typeof Scope,
): Promise<SubscribeResponse> {
if ((await this.ecgRtrListService.getContainedEcgRtrListEmails([data.email])).length > 0) {
return SubscribeResponse.ERROR_CONTAINED_IN_ECG_RTR_LIST;
}

const created = await this.brevoContactsService.createDoubleOptInContact({
...data,
scope,
templateId: this.config.brevo.resolveConfig(scope).doubleOptInTemplateId,
});

if (created) {
return SubscribeResponse.SUCCESSFUL;
}

return SubscribeResponse.ERROR_UNKNOWN;
return this.brevoContactsService.subscribeBrevoContact(data, scope);
}
}

Expand Down
32 changes: 30 additions & 2 deletions packages/api/src/brevo-contact/brevo-contacts.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Injectable } from "@nestjs/common";
import { Inject, Injectable } from "@nestjs/common";

import { BrevoApiContactsService } from "../brevo-api/brevo-api-contact.service";
import { BrevoModuleConfig } from "../config/brevo-module.config";
import { BREVO_MODULE_CONFIG } from "../config/brevo-module.constants";
import { TargetGroupsService } from "../target-group/target-groups.service";
import { BrevoContactAttributesInterface, EmailCampaignScopeInterface } from "../types";
import { SubscribeInputInterface } from "./dto/subscribe-input.factory";
import { SubscribeResponse } from "./dto/subscribe-response.enum";
import { EcgRtrListService } from "./ecg-rtr-list/ecg-rtr-list.service";

@Injectable()
export class BrevoContactsService {
constructor(private readonly brevoContactsApiService: BrevoApiContactsService, private readonly targetGroupService: TargetGroupsService) {}
constructor(
@Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig,
private readonly brevoContactsApiService: BrevoApiContactsService,
private readonly ecgRtrListService: EcgRtrListService,
private readonly targetGroupService: TargetGroupsService,
) {}

public async createDoubleOptInContact({
email,
Expand Down Expand Up @@ -61,4 +71,22 @@ export class BrevoContactsService {

return targetGroupIds;
}

public async subscribeBrevoContact(data: SubscribeInputInterface, scope: EmailCampaignScopeInterface): Promise<SubscribeResponse> {
if ((await this.ecgRtrListService.getContainedEcgRtrListEmails([data.email])).length > 0) {
return SubscribeResponse.ERROR_CONTAINED_IN_ECG_RTR_LIST;
}

const created = await this.createDoubleOptInContact({
...data,
scope,
templateId: this.config.brevo.resolveConfig(scope).doubleOptInTemplateId,
});

if (created) {
return SubscribeResponse.SUCCESSFUL;
}

return SubscribeResponse.ERROR_UNKNOWN;
}
}
1 change: 1 addition & 0 deletions packages/api/src/brevo-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class BrevoModule {
BrevoContactAttributes: config.brevo.BrevoContactAttributes,
Scope: config.emailCampaigns.Scope,
TargetGroup,
enablePublicApiSubscriptionRoute: config.enablePublicApiSubscriptionRoute,
}),
EmailCampaignModule.register({
EmailCampaignContentBlock: config.emailCampaigns.EmailCampaignContentBlock,
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/config/brevo-module.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export interface BrevoModuleConfig {
ecgRtrList: {
apiKey: string;
};

emailCampaigns: {
Scope: Type<EmailCampaignScopeInterface>;
EmailCampaignContentBlock: Block;
Expand All @@ -33,4 +32,5 @@ export interface BrevoModuleConfig {
};
};
};
enablePublicApiSubscriptionRoute?: boolean;
}

0 comments on commit ef65801

Please sign in to comment.